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

Make GC triggering and heap resizing consistent #1266

Merged
merged 3 commits into from
Jan 21, 2025

Conversation

wks
Copy link
Collaborator

@wks wks commented Jan 20, 2025

This PR fixes a bug where the MemBalancer did not increase the heap size enough to accommodate the amount of side metadata needed by the pending allocation. It manifested as looping infinitely between triggering GC and (not actually) resizing the heap size after a GC when the minimum heap size is too small.

Now it always includes the side metadata amount when increasing heap size.

This PR also refactors the calculation of "shifting right and rounding up" which is used in multiple places. We also replace alloc_rshift with log_data_meta_ratio for two reasons. (1) The previous implementation would cause unsigned overflow before converting the result to i32. (2) log_data_meta_ratio has clearer semantics.

wks added 3 commits January 20, 2025 15:43
It makes more sense.  We also add conversion functions between data and
meta sizes.
We count size metadata in when reporting pending allocation size to the
GC trigger.  This ensures the MemBalancer increases the heap size
by the same amount (or larger due to over-estimation) as estimated by
`Space::reserved_pages`.
@wks wks requested a review from qinsoon January 21, 2025 02:04
@qinsoon qinsoon added the PR-extended-testing Run extended tests for the pull request label Jan 21, 2025
Copy link
Member

@qinsoon qinsoon left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -313,9 +316,20 @@ pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
.mark_as_mapped(self.common().start, self.common().extent);
}

/// Estimate the amount of side metadata memory needed for a give data memory size in pages. The
/// result will over-estimate the amount of metadata pages needed, with at least one page per
Copy link
Member

Choose a reason for hiding this comment

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

The following is not an issue from this PR, but I think it is worth bringing it up and discussing it.

Rounding up to the nearest page is okay. The real issue is that we may double count metadata memory.
Consider the case that we have 8 data pages, and the ratio between data and side metedata is 8:1. We actually have 1 side metadata page. If we call this function with 8 pages, we get 1 page -- good. But if we call this function 4 times with 2 pages each, we get 4 side metadata pages.

To estimate the side metadata pages for spaces, we go through each space, and calculate the side metadata usage for each spec in the space. The more spaces and the specs we have, the larger error we have for the calculated side metadata pages. Also if we divide the space into smaller ranges and call this function for each range, we get larger errors. This PR uses this function for pending pages, which is a relatively small memory range (compared to spaces). Luckily we do not add up the estimated pending pages, so probably it is fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To estimate the side metadata pages for spaces, we go through each space, and calculate the side metadata usage for each spec in the space. The more spaces and the specs we have, the larger error we have for the calculated side metadata pages.

This is actually expected. Spaces do not overlap (Their ranges don't overlap in Map64. In Map32 with discontiguous spaces, spaces do not overlap at chunk granularity). Therefore side metadata for different spaces will not overlap. So if there are four different spaces, each haing 2 pages of data memory, and the data-meta ratio is 8:1, it will still need 4 pages of metadata, one for each space, despite that each space only needs 0.25 pages of metadata.

Also if we divide the space into smaller ranges and call this function for each range, we get larger errors. This PR uses this function for pending pages, which is a relatively small memory range (compared to spaces). Luckily we do not add up the estimated pending pages, so probably it is fine.

Yes. Pending pages are usually small, and it is almost certainly that the result will be an over-estimation. But in reality, if the allocated pending pages happen to be in a region where no side metadata has been mapped, yet, it will allocate one page in each kind of side metadata. So this kind of over-estimation covers the worst-case scenario.

But it is still arguable whether over-estimating at page granularity is the best choice. The operating system does paging at page granularity, which can be seen in /proc/pid/pagemap, as we discussed before. But MMTk does mmap at chunk granularity. Since each chunk is 4MB, that'll over-estimate the memory use to much. Page granularity happens to just work, as long as GC triggering and heap resizing are consistent with the estimation.

@qinsoon
Copy link
Member

qinsoon commented Jan 21, 2025

@wks Did you verify that this PR fixes the Ruby issue?

@wks
Copy link
Collaborator Author

wks commented Jan 21, 2025

@wks Did you verify that this PR fixes the Ruby issue?

Yes. With 100KiB heap size, it can continue from the first GC and finish the program.

$ RUBY_GC_LIBRARY=mmtk MMTK_HEAP_MIN=100KiB ./ruby -e ""
[2025-01-21T05:06:02Z INFO  mmtk::memory_manager] Initialized MMTk with MarkSweep (DynamicHeapSize(102400, 26654991974))
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (26/25 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (0/26 pages, took 0 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (42/26 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (26/54 pages, took 4 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (58/54 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (42/125 pages, took 8 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (140/125 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (124/183 pages, took 12 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (190/183 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (158/206 pages, took 31 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (224/206 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (174/241 pages, took 30 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (256/241 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (224/276 pages, took 21 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (290/276 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (272/336 pages, took 25 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (338/336 pages)
[2025-01-21T05:06:02Z INFO  mmtk::scheduler::scheduler] End of GC (322/384 pages, took 26 ms)
[2025-01-21T05:06:02Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (388/384 pages)
[2025-01-21T05:06:03Z INFO  mmtk::scheduler::scheduler] End of GC (372/420 pages, took 27 ms)
[2025-01-21T05:06:03Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (422/420 pages)
[2025-01-21T05:06:03Z INFO  mmtk::scheduler::scheduler] End of GC (404/448 pages, took 26 ms)
[2025-01-21T05:06:03Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (454/448 pages)
[2025-01-21T05:06:03Z INFO  mmtk::scheduler::scheduler] End of GC (438/485 pages, took 24 ms)

The heap size is 25 pages. The first GC is triggered at 26 pages (of reserved pages), and the heap size has already been adjusted to 26 pages when it triggers GC the second time. In the second GC, the reserved pages is 42 pages, 16 pages greater than 26, which means it is trying to allocate the second MarkSweep block.

If I start with 104KiB of min heap size, the log will look like this:

RUBY_GC_LIBRARY=mmtk MMTK_HEAP_MIN=104KiB ./ruby -e ""
[2025-01-21T05:05:59Z INFO  mmtk::memory_manager] Initialized MMTk with MarkSweep (DynamicHeapSize(106496, 26654991974))
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (42/26 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (26/56 pages, took 2 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (58/56 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (42/73 pages, took 2 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (74/73 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (58/108 pages, took 12 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (124/108 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (108/154 pages, took 19 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (158/154 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (140/185 pages, took 18 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (190/185 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (158/206 pages, took 12 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (224/206 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (174/234 pages, took 15 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (240/234 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (206/286 pages, took 43 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (290/286 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (272/338 pages, took 20 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (356/338 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (338/384 pages, took 25 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (388/384 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (372/418 pages, took 34 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (422/418 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (404/450 pages, took 34 ms)
[2025-01-21T05:05:59Z INFO  mmtk::util::heap::gc_trigger] [POLL] ms: Triggering collection (454/450 pages)
[2025-01-21T05:05:59Z INFO  mmtk::scheduler::scheduler] End of GC (438/487 pages, took 48 ms)

It will trigger GC when allocating the second block, and it will move on like usual until completion.

@wks wks added this pull request to the merge queue Jan 21, 2025
Merged via the queue into mmtk:master with commit 051bc74 Jan 21, 2025
38 of 40 checks passed
@wks wks deleted the fix/pending-alloc-meta branch January 21, 2025 06:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR-extended-testing Run extended tests for the pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants