Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch the default allocation policy to best-fit. #10188

Merged
merged 4 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changes
Expand Up @@ -39,6 +39,10 @@ Working version
- #10136: Minor clean-ups in runtime/io.c and runtime/caml/io.h
(Xavier Leroy, review by David Allsopp and Guillaume Munch-Maccagnoni)

- #10188: Switch the default allocation policy to best-fit and adjust the
default overhead parameter accordingly.
(Damien Doligez, review by xxx)

### Code generation and optimizations:

- #9876: do not cache the young_limit GC variable in a processor register.
Expand Down
3 changes: 1 addition & 2 deletions man/ocamlrun.m
Expand Up @@ -151,8 +151,7 @@ The initial size of the major heap (in words).
.BR a \ (allocation_policy)
The policy used for allocating in the OCaml heap. Possible values
are 0 for the next-fit policy, 1 for the first-fit
policy, and 2 for the best-fit policy. Best-fit is still experimental,
but probably the best of the three. The default is 0.
policy, and 2 for the best-fit policy. The default is 2.
See the Gc module documentation for details.
.TP
.BR s \ (minor_heap_size)
Expand Down
3 changes: 1 addition & 2 deletions manual/src/cmds/runtime.etex
Expand Up @@ -140,8 +140,7 @@ The following environment variables are also consulted:
\item[a] ("allocation_policy")
The policy used for allocating in the OCaml heap. Possible values
are "0" for the next-fit policy, "1" for the first-fit
policy, and "2" for the best-fit policy. Best-fit is still experimental,
but probably the best of the three. The default is "0" (next-fit).
policy, and "2" for the best-fit policy. The default is "2" (best-fit).
See the Gc module documentation for details.
\item[s] ("minor_heap_size") Size of the minor heap. (in words)
\item[i] ("major_heap_increment") Default size increment for the
Expand Down
2 changes: 1 addition & 1 deletion runtime/caml/config.h
Expand Up @@ -240,7 +240,7 @@ typedef uint64_t uintnat;
/* Default speed setting for the major GC. The heap will grow until
the dead objects and the free list represent this percentage of the
total size of live objects. */
#define Percent_free_def 80
#define Percent_free_def 100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a data point, in the systems I have tested, 120 has been notably faster without a measurably significant increase in memory usage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To concur, Coq sets 200 with the best-fit, and when I asked they seemed happy with it, so 120 is not shocking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At 200, memory use can increase significantly, but 120 seems like a good trade-off.

According to some measurements of the two (see https://blog.janestreet.com/memory-allocator-showdown/), best-fit at 100 has similar performance to next-fit at 80 while using less memory. This means that the gains from switching to best-fit are entirely allocated to reducing memory use, while at 120 the gains are split between reducing memory use and reducing time spent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Personally I think that the setting of 200 for Coq was a bit too aggressive. It is a very memory-hungry program, and while everyone is able to wait 10% longer to get their proof to complete, your machine may not necessarily be able to give 10% more memory to the process. I think this hard-limit behavior of memory would suggest a more conservative default, but I guess heavy Coq-users don't have the same perspective as they bought large-memory machines anyway.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @gasche: let's have default values that don't waste too much memory, because running out of memory and starting to swap is very costly indeed.


/* Default setting for the compacter: 500%
(i.e. trigger the compacter when 5/6 of the heap is free or garbage)
Expand Down
2 changes: 1 addition & 1 deletion runtime/freelist.c
Expand Up @@ -1754,7 +1754,7 @@ enum {
policy_best_fit = 2,
};

uintnat caml_allocation_policy = policy_next_fit;
uintnat caml_allocation_policy = policy_best_fit;

/********************* exported functions *****************************/

Expand Down
21 changes: 7 additions & 14 deletions stdlib/gc.mli
Expand Up @@ -108,7 +108,7 @@ type control =
percentage of the memory used for live data.
The GC will work more (use more CPU time and collect
blocks more eagerly) if [space_overhead] is smaller.
Default: 80. *)
Default: 100. *)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also be changed from 100 to 120? @damiendoligez?


mutable verbose : int;
[@ocaml.deprecated_mutable "Use {(Gc.get()) with Gc.verbose = ...}"]
Expand Down Expand Up @@ -164,30 +164,23 @@ type control =
memory than both next-fit and first-fit.
(since OCaml 4.10)

The current default is next-fit, as the best-fit policy is new
and not yet widely tested. We expect best-fit to become the
default in the future.
The default is best-fit.

On one example that was known to be bad for next-fit and first-fit,
next-fit takes 28s using 855Mio of memory,
first-fit takes 47s using 566Mio of memory,
best-fit takes 27s using 545Mio of memory.

Note: When changing to a low-fragmentation policy, you may
need to augment the [space_overhead] setting, for example
using [100] instead of the default [80] which is tuned for
next-fit. Indeed, the difference in fragmentation behavior
means that different policies will have different proportion
of "wasted space" for a given program. Less fragmentation
means a smaller heap so, for the same amount of wasted space,
a higher proportion of wasted space. This makes the GC work
harder, unless you relax it by increasing [space_overhead].
Note: If you change to next-fit, you may need to reduce
the [space_overhead] setting, for example using [80] instead
of the default [100] which is tuned for best-fit. Otherwise,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this

your program will need more memory.

Note: changing the allocation policy at run-time forces
a heap compaction, which is a lengthy operation unless the
heap is small (e.g. at the start of the program).

Default: 0.
Default: 2.

@since 3.11.0 *)

Expand Down