You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Optimize garbage collection with a generational GC
Running jit tests with AtomVM is now 20% faster.
Implement BEAM's `fullsweep_after` `spawn_opt/1` option and `process_flag/2`
flag. Also fix `process_flag/2` spec.
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
Copy file name to clipboardExpand all lines: doc/src/memory-management.md
+37Lines changed: 37 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -922,3 +922,40 @@ match binaries, as with the case of refc binaries on the process heap.
922
922
#### Deletion
923
923
924
924
Once all terms have been copied from the old heap to the new heap, and once the MSO list has been swept for unreachable references, the old heap is simply discarded via the `free` function.
925
+
926
+
### Generational Garbage Collection
927
+
928
+
The garbage collection described above is a *full sweep*: every live term is copied from the old heap to the new heap and the entire old heap is freed. While correct, this can be expensive for processes with large heaps, because long-lived data that has already survived previous collections must be copied again each time.
929
+
930
+
AtomVM implements *generational* (or *minor*) garbage collection to reduce this cost, using the same approach as BEAM. The key observation is that most terms die young: they are allocated, used briefly, and become garbage. Terms that have survived at least one collection are likely to survive many more. Generational GC exploits this by dividing the heap into two generations:
931
+
932
+
***Young generation**: recently allocated terms, between the *high water mark* and the current heap pointer.
933
+
***Old (mature) generation**: terms that have survived at least one minor collection, stored in a separate old heap.
934
+
935
+
#### High Water Mark
936
+
937
+
After each garbage collection, the heap pointer position is recorded as the *high water mark*. On the next collection, terms allocated below the high water mark (i.e., terms that existed at the time of the previous collection) are considered mature. Terms allocated above the high water mark are young.
938
+
939
+
#### Minor Collection
940
+
941
+
During a minor collection:
942
+
943
+
1. A new young heap is allocated.
944
+
2. Mature terms (below the high water mark) are *promoted*: copied to the old heap rather than the new young heap.
945
+
3. Young terms that are still reachable are copied to the new young heap.
946
+
4. Both the new young heap and the newly promoted old region are scanned for references, since promoted terms may reference young terms and vice versa.
947
+
5. Only the young MSO list is swept; the old MSO list is preserved.
948
+
6. The previous heap is freed, but the old heap persists across minor collections.
949
+
950
+
Because the old heap is not scanned for garbage during a minor collection, the cost is proportional to the size of the young generation rather than the entire heap.
951
+
952
+
#### When Full vs. Minor Collection Occurs
953
+
954
+
AtomVM keeps a counter (`gc_count`) of how many minor collections have occurred since the last full sweep. A full sweep is forced when:
955
+
956
+
* The process has never been garbage collected (no high water mark exists).
957
+
*`gc_count` reaches the `fullsweep_after` threshold.
958
+
* The old heap does not have enough space to accommodate promoted terms.
959
+
* A `MEMORY_FORCE_SHRINK` request is made (e.g., via `erlang:garbage_collect/0`).
960
+
961
+
The `fullsweep_after` value can be set per-process via [`spawn_opt`](./programmers-guide.md#spawning-processes) or [`erlang:process_flag/2`](./apidocs/erlang/estdlib/erlang.md#process_flag2). The default value is 65535, meaning full sweeps are infrequent under normal operation. Setting it to `0` disables generational collection entirely, forcing a full sweep on every garbage collection event.
|`min_heap_size`|`non_neg_integer()`| none | Minimum heap size of the process. The heap will shrink no smaller than this size. |
351
351
|`max_heap_size`|`non_neg_integer()`| unbounded | Maximum heap size of the process. The heap will grow no larger than this size. |
352
+
|`fullsweep_after`|`non_neg_integer()`| 65535 | Maximum number of [minor garbage collections](./memory-management.md#generational-garbage-collection) before a full sweep is forced. Set to `0` to disable generational garbage collection. |
352
353
|`link`|`boolean()`|`false`| Whether to link the spawned process to the spawning process. |
353
354
|`monitor`|`boolean()`|`false`| Whether to link the spawning process should monitor the spawned process. |
354
355
|`atomvm_heap_growth`|`bounded_free \| minimum \| fibonacci`|`bounded_free`|[Strategy](./memory-management.md#heap-growth-strategies) to grow the heap of the process. |
0 commit comments