-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Decommit region tails #66008
Decommit region tails #66008
Conversation
Tagging subscribers to this area: @dotnet/gc Issue DetailsI observed that with gen 1 regions, we often get into the situation that gen 1 is much smaller per heap than a region. So it makes sense to decommit the tail end of the last region in an ephemeral generation guided by the budget for that generation. To implement this, I reactivated decommit_target for regions and have decommit_step call decommit_ephemeral_segment_pages_step which in the regions case needs to synchronize with the allocator. This is done by taking the more space lock. Note that with default settings, this decommitting logic will usually only apply to gen 1 because normally gen 0 is larger than a region. It can still happen for gen 0 though if gen 0 has pins and thus already has enough space to satisfy the budget. Then we will decommit the tail end of the last region in gen 0.
|
@@ -39572,14 +39570,55 @@ void gc_heap::decommit_ephemeral_segment_pages() | |||
} | |||
|
|||
#if defined(MULTIPLE_HEAPS) && defined(USE_REGIONS) | |||
// for regions, this is done at the regions level | |||
return; | |||
for (int gen_number = soh_gen0; gen_number <= soh_gen1; gen_number++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we are not even doing a gen1 GC, would we need to goto soh_gen1
here?
if we are doing a gen1 GC we would have called decommit_heap_segment_pages
and decommitted (almost) everything after heap_segment_allocated
for gen1 regions -
if (gen_num != 0)
{
dprintf (REGIONS_LOG, (" gen%d decommit end of region %Ix(%Ix)",
gen_num, current_region, heap_segment_mem (current_region)));
decommit_heap_segment_pages (current_region, 0);
}
if dt_high_memory_load_p()
is true. so you could avoid doing the gen1 region in that case. however, I'm also fine if you want to avoid complicating this code path here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now we are not decommitting end of region space for gen2 since it's only doing the decommitting when memory load is high, this could still make a difference for small benchmarks (as in, regions would use more memory)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two reasons for considering the situation in soh_gen1
even if we are not doing a gen 1 GC:
- The free list situation in gen 1 could have changed.
- We are smoothing the ramp down, and gen 1 GCs may happen too infrequently. The alternative would be to not do the smoothing, but I think that would likely perform worse in cases where the work load changes behavior.
We need to consider what to do with gen 2 (and UOH as well), agreed. This should be a separate PR, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually I think for now we should keep the behavior for decommit_heap_segment_pages
for now, for gen2/UOH - there's no reason not to because that's only done during a full blocking GC. and it's the same behavior for segments. we can make a separate PR to improve this behavior.
if ((allocated <= decommit_target) && (decommit_target < committed)) | ||
{ | ||
#ifdef USE_REGIONS | ||
enter_spin_lock (&more_space_lock_soh); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not take the msl if gen_number
is soh_gen1
since it's unnecessary. taking the msl here is kind of worrisome for perf - this thread is running at THREAD_PRIORITY_HIGHEST
which means it will actually likely keep running till it's done with all the decommit work here (this also means the decommit for regions on the decommit list might occupy heap0's core for quite some time before it can be used for anything else). while lower priority threads can run on other cores (and most likely will get to), it's still good to be conscientious about not holding up one core for too long. we do call SetThreadIdealProcessorEx on heap0's proc when we want to allocate on heap0 but that's just the ideal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I skipped taking the msl lock for soh_gen1
.
Regarding tying up GC thread 0, hopefully we won't be tying it up for very long, because we limit the amount of decommit to max_decommit_step_size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the rest LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new changes look good to me
- add initialization for heap_segment_decommit_target - check for use_large_pages_p in decommit_step
- for the ephemeral_heap_segment, need to get allocated from the heap, not from the segment - calling enter_spin_lock may deadlock at the start of a GC, replaced by try_enter_spin_lock
I agree with both fixes. thanks! |
one minor thing we could do is to save the tail_region that GC saw and in case it changed, we can simply stop gradual decommit instead of checking the new tail_region. |
If we added a new tail region, then I think this region must have gone through init_heap_segment which sets heap_segment_decommit_target to heap_segment_reserved. So we shouldn't decommit in this case. So the net effect of not proceeding with decommit should already happen. |
I observed that with gen 1 regions, we often get into the situation that gen 1 is much smaller per heap than a region. So it makes sense to decommit the tail end of the last region in an ephemeral generation guided by the budget for that generation.
To implement this, I reactivated decommit_target for regions and have decommit_step call decommit_ephemeral_segment_pages_step which in the regions case needs to synchronize with the allocator. This is done by taking the more space lock.
Note that with default settings, this decommitting logic will usually only apply to gen 1 because normally gen 0 is larger than a region. It can still happen for gen 0 though if gen 0 has pins and thus already has enough space to satisfy the budget. Then we will decommit the tail end of the last region in gen 0.