Compacting GC: thread backwards pointers in marking#2647
Compacting GC: thread backwards pointers in marking#2647mergify[bot] merged 12 commits intomasterfrom
Conversation
|
This PR does not affect the produced WebAssembly code. |
|
CanCan backend bench reports -19.8% instructions compared to master. |
rts/motoko-rts-tests/src/gc.rs
Outdated
| ) { | ||
| let heap = MotokoHeap::new(refs, roots, closure_table, gc); | ||
|
|
||
| // Check `check_dynamic_heap` sanity |
There was a problem hiding this comment.
This should be "create_dynamic_heap sanity".
| heap.heap_base_offset(), | ||
| heap.heap_ptr_offset(), | ||
| heap.closure_table_ptr_offset(), | ||
| ); |
There was a problem hiding this comment.
Same as previous PR, this is removed as check_dynamic_heap rejects garbage, but we want to test heaps with garbage as well. So currently check_dynamic_heap is only called after a GC, not before.
| let field_addr = &mut (*mutbox).field; | ||
| // TODO: Not sure if this check is necessary? | ||
| if pointer_to_dynamic_heap(field_addr, heap_base as usize) { | ||
| // TODO: We should be able to omit the "already marked" check here as no two root MutBox |
There was a problem hiding this comment.
That's probably true, but I'd check with Joachim first. Or just play it safe. Does the set of root boxes ever get large? My intuition is that it corresponds to top-level vars and recursive declarations that need back-patching, but I'm not sure.
crusso
left a comment
There was a problem hiding this comment.
I think I'll need some handholding to convince me.
| } | ||
| // No need to thread closure table here as it's on heap and we already marked it | ||
| // Thread if backwards pointer | ||
| if field_value.unskew() < obj as usize { |
There was a problem hiding this comment.
I though I had commented on this, but perhaps I forgot to submit.
Is there any danger you thread the same field twice, if you've already visited the object?
There was a problem hiding this comment.
We should not visit an object twice. This function is called on values popped from mark stack, and we mark objects when pushing to mark stack and do not push marked objects to the mark stack. So a field should only be threaded once.
This PR implements the mark-compact GC algorithm briefly described by
"High-Performance Garbage Collection for Memory-Constrained Environments"
section 5.1.2 (I think the original paper is "An Efficient Garbage Compaction
Algorithm" but that paper is a bit difficult to follow).
The idea is as follows: when marking an object we thread backwards pointers of
that object. After we linearly scan the heap as before. For a live object, we
unthread it and update backwards pointers to it to the objects new location,
move the object, and then thread its forwards pointers. After the pass all
objects are moved and all references are updated.
This reduces number of passes in the original mark-compact collector to 2.
CanCan backend benchmark reports -19.8% in instructions. Compared to copying
GC, mark-compact GC is now 36% slower, instead of 70% as before.
This PR also optimizes
mark_static_rootsby inliningmark_fieldsin thebody and specializing it for
MutBox: static root array only points to staticMutBoxes so no need to call more generalmark_fields. This optimizationgives us approximately -2% in instructions.
See also #2641 for another, slower variant of the algorithm.