mmtk / mmtk-core

Memory Management ToolKit
https://www.mmtk.io
Other
379 stars 69 forks source link

Heap traversal #1174

Closed wks closed 3 months ago

wks commented 4 months ago

This PR adds a heap traversal API MMTK::enumerate_objects which enumerates all objects in the MMTk heap at the time of calling.

We added SideMetadataSpec::scan_non_zero_values to support enumerating objects by scanning the VO bit metadata. It can also be used for scanning other side metadata when needed. Note, however, that it is inefficient (but should work correctly) if the metadata is not contiguous or the metadata has more than one bit per region. If there is any need for scanning such metadata, we need further refactoring.

wks commented 4 months ago

This is my first attempt to create a heap traversal API. The implementation is probably right, but we may need to refactor our current mechanism for visiting metadata in bulk (including reading, zeroing, setting, copying, as well as finding the last non-zero value, and finding all non-zero values in a range for heap traversal). The main problem is that iterate_meta_bits has two call-back closures, and they can't share mutable borrows well. We have already seen its consequence because find_prev_non_zero_value_fast needs to use a Cell to store the return value. Heap traversal will need to use a Cell or RefCell for similar reason, and that won't perform well.

And things can be further simplified if we unify ObjectReference and the "in-object address" (see https://github.com/mmtk/mmtk-core/issues/1170). Then the linear scanner will no longer depend on the <VM: VMBinding> type (for using ObjectReference::from_address<VM>).

wks commented 3 months ago

... The main problem is that iterate_meta_bits has two call-back closures, and they can't share mutable borrows well. We have already seen its consequence because find_prev_non_zero_value_fast needs to use a Cell to store the return value. Heap traversal will need to use a Cell or RefCell for similar reason, and that won't perform well.

I reimplemented it upon https://github.com/mmtk/mmtk-core/pull/1181 and now we no longer use RefCell.

Benchmark (taskset -c 0 cargo bench --features test_private -- bscan) shows that it can scan an Immix block with 200 objects in 268 ns in the steady state. In reality, it may take much longer due to cache misses.

GitHub CI reported an added API function SideMetadataSpec::scan_non_zero_values which is intended. It did not report the added public method MMTK::enumerate_objects, probably because it is guarded by the feature "vo_bit".

wks commented 3 months ago

We have an old ObjectIterator, which is very similar to the simple version of scan_non_zero_values (expect that it skips the object size when it finds an object). After introducing trait ObjectEnumerator, it becomes confusing that we have ObjectIterator and ObjectEnuemrator and they have nothing to do with each other.

It would be good if we can simply remove ObjectIterator and replace its uses with the newly introduced enumerator. If this cannot be done in this PR, we should at least document the code to state what we plan to do with the old ObjectIterator.

I think linear_scan::ObjectIterator works similarly as the algorithm in this PR. ObjectIterator outputs ObjectReference, too, and it also scans from low to high addresses. A minor difference is that ObjectIterator attempts to jump a longer distance by calling ObjectModel::get_current_size. I am not sure whether ObjectModel::get_current_size is faster than blindly scanning the VO bits at word granularity. We need benchmarks for that.

Since removing ObjectIterator will involve refactoring MarkCompact, too, I'll do it in a separate PR.