Compaction should prioritise pages with more pinned objects #8420
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
In Ruby 2.7 compaction was originally introduced as an experimental feature.
There was no auto-compaction, and compaction had to be manually triggered using
GC.compact
.See this commit.
When this happened, at the start of compaction the heap was sorted so that pages
containing the most pinned slots were compacted into by
default.
This was important for compaction efficiency and copy on write friendliness as
it prioritises filling pages that can't be free'd and emptying as many pages as possible.
When
GC.auto_compact
, was introduced in this commit. This heap sortingmechanism was (unintentionally?) removed.
This means that the heap is now unsorted when compacted and we're just
compacting towards the lowest addresses from the higher ones.
This PR re-introduces this feature: by default the heap will now be sorted by
pinned object count prior to compaction.
This will improve the CoW friendliness of compaction to aid the reforking work
being carried out by byroot.
Verifying the behaviour
We used a test script to build a heap that would be laid out in a prescribed
manner. with a predictable pinning pattern in order to test that pages with
higher numbers of pinned objects are being filled first during compaction.
Test script was as follows:
Heap diagram before
This diagram shows heap pages vertically with the pinned object distribution pattern set up by the test script.
Heap diagram after (master branch)
This diagram shows the heap having been compacted without this patch. We can see that the heap has not been sorted by pages, and that movable objects (green) are being compacted into pages that contain fewer pinned objects, and leaving pages that contain lots of pinned objects sparsely populated.
Heap diagram after (this branch)
This diagram shows the heap having been compacted into pinned pages. This diagram shows pages sorted by their order in the
heap->pages
list. So will respect the sorting that happens during compaction.Benchmarking
Compaction speed
This plot shows mean and standard deviation of
GC.compact
time for 6 runs of the test script defined above on both this branch and master. Measured usingBenchmark.bm
Raw values are:
This data appears shows a 3% slowdown in GC compation times on this branch which is expected given the extra work required to sort the heap prior to compaction.
However, given the sizeable overlap in the error bars we can conclude that this difference is not significant.
Post compaction RSS
We used a modified test script that created a heap with a lot of pinned slots at the end of the heap.
The expected outcome is that if the heap remains unsorted, RSS will be higher because objects will be packed into the front of the heap, and then followed by sparsely populated pages of pinned objects.
In a sorted heap, the objects will be packed in around the pages of pinned objects where possible allowing more empty pages to be free'd and RSS to be lower.
Here is the test script used:
And here is the outcome:
Raw data:
We can see from this data that sorting the heap by pinned pages results in a 1.5% decrease in memory usage, and we can infer from the non-overlapping error bars that this is likely to be statistically significant.
Conclusion
Re-introducing heap sorting by pinned slots prior to compaction improves memory usage of a Ruby process by a statistically significant amount. It does this at a very slight, and likely insignificant performance penalty, which we believe to be acceptable given that GC compaction is currently a fairly major "stop the world" event that should only be done occasionally. For example, before forking.