Skip to content

Commit 942430c

Browse files
authored
Decommit region tails (#66008)
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.
1 parent 0022738 commit 942430c

File tree

2 files changed

+147
-48
lines changed

2 files changed

+147
-48
lines changed

src/coreclr/gc/gc.cpp

Lines changed: 143 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11431,6 +11431,7 @@ void gc_heap::init_heap_segment (heap_segment* seg, gc_heap* hp
1143111431
heap_segment_plan_allocated (seg) = heap_segment_mem (seg);
1143211432
heap_segment_allocated (seg) = heap_segment_mem (seg);
1143311433
heap_segment_saved_allocated (seg) = heap_segment_mem (seg);
11434+
heap_segment_decommit_target (seg) = heap_segment_reserved (seg);
1143411435
#ifdef BACKGROUND_GC
1143511436
heap_segment_background_allocated (seg) = 0;
1143611437
heap_segment_saved_bg_allocated (seg) = 0;
@@ -11546,6 +11547,7 @@ void gc_heap::decommit_heap_segment_pages (heap_segment* seg,
1154611547
{
1154711548
if (use_large_pages_p)
1154811549
return;
11550+
1154911551
uint8_t* page_start = align_on_page (heap_segment_allocated(seg));
1155011552
assert (heap_segment_committed (seg) >= page_start);
1155111553

@@ -11561,12 +11563,6 @@ void gc_heap::decommit_heap_segment_pages (heap_segment* seg,
1156111563
size_t gc_heap::decommit_heap_segment_pages_worker (heap_segment* seg,
1156211564
uint8_t* new_committed)
1156311565
{
11564-
#ifdef USE_REGIONS
11565-
if (!dt_high_memory_load_p())
11566-
{
11567-
return 0;
11568-
}
11569-
#endif
1157011566
assert (!use_large_pages_p);
1157111567
uint8_t* page_start = align_on_page (new_committed);
1157211568
ptrdiff_t size = heap_segment_committed (seg) - page_start;
@@ -12351,8 +12347,7 @@ void gc_heap::distribute_free_regions()
1235112347
heap_budget_in_region_units[i][large_free_region] = 0;
1235212348
for (int gen = soh_gen0; gen < total_generation_count; gen++)
1235312349
{
12354-
ptrdiff_t budget_gen = hp->estimate_gen_growth (gen);
12355-
assert (budget_gen >= 0);
12350+
ptrdiff_t budget_gen = max (hp->estimate_gen_growth (gen), 0);
1235612351
int kind = gen >= loh_generation;
1235712352
size_t budget_gen_in_region_units = (budget_gen + (region_size[kind] - 1)) / region_size[kind];
1235812353
dprintf (REGIONS_LOG, ("h%2d gen %d has an estimated growth of %Id bytes (%Id regions)", i, gen, budget_gen, budget_gen_in_region_units));
@@ -12499,7 +12494,6 @@ void gc_heap::distribute_free_regions()
1249912494
}
1250012495

1250112496
#ifdef MULTIPLE_HEAPS
12502-
gradual_decommit_in_progress_p = FALSE;
1250312497
for (int kind = basic_free_region; kind < count_free_region_kinds; kind++)
1250412498
{
1250512499
if (global_regions_to_decommit[kind].get_num_free_regions() != 0)
@@ -22142,7 +22136,7 @@ void gc_heap::garbage_collect (int n)
2214222136
}
2214322137

2214422138
descr_generations ("BEGIN");
22145-
#ifdef TRACE_GC
22139+
#if defined(TRACE_GC) && defined(USE_REGIONS)
2214622140
if (heap_number == 0)
2214722141
{
2214822142
#ifdef MULTIPLE_HEAPS
@@ -22166,7 +22160,7 @@ void gc_heap::garbage_collect (int n)
2216622160
}
2216722161
}
2216822162
}
22169-
#endif // TRACE_GC
22163+
#endif // TRACE_GC && USE_REGIONS
2217022164

2217122165
#ifdef VERIFY_HEAP
2217222166
if ((GCConfig::GetHeapVerifyLevel() & GCConfig::HEAPVERIFY_GC) &&
@@ -30221,7 +30215,7 @@ heap_segment* gc_heap::find_first_valid_region (heap_segment* region, bool compa
3022130215
set_region_plan_gen_num (current_region, plan_gen_num);
3022230216
}
3022330217

30224-
if (gen_num != 0)
30218+
if (gen_num >= soh_gen2)
3022530219
{
3022630220
dprintf (REGIONS_LOG, (" gen%d decommit end of region %Ix(%Ix)",
3022730221
gen_num, current_region, heap_segment_mem (current_region)));
@@ -39562,7 +39556,7 @@ ptrdiff_t gc_heap::estimate_gen_growth (int gen_number)
3956239556
gen_number, heap_number, budget_gen, new_allocation_gen, free_list_space_gen));
3956339557
#endif //USE_REGIONS
3956439558

39565-
return max(0, budget_gen);
39559+
return budget_gen;
3956639560
}
3956739561

3956839562
void gc_heap::decommit_ephemeral_segment_pages()
@@ -39573,14 +39567,71 @@ void gc_heap::decommit_ephemeral_segment_pages()
3957339567
}
3957439568

3957539569
#if defined(MULTIPLE_HEAPS) && defined(USE_REGIONS)
39576-
// for regions, this is done at the regions level
39577-
return;
39570+
for (int gen_number = soh_gen0; gen_number <= soh_gen1; gen_number++)
39571+
{
39572+
generation *gen = generation_of (gen_number);
39573+
heap_segment* tail_region = generation_tail_region (gen);
39574+
uint8_t* previous_decommit_target = heap_segment_decommit_target (tail_region);
39575+
39576+
// reset the decommit targets to make sure we don't decommit inadvertently
39577+
for (heap_segment* region = generation_start_segment_rw (gen); region != nullptr; region = heap_segment_next (region))
39578+
{
39579+
heap_segment_decommit_target (region) = heap_segment_reserved (region);
39580+
}
39581+
39582+
ptrdiff_t budget_gen = estimate_gen_growth (gen_number) + loh_size_threshold;
39583+
39584+
if (budget_gen >= 0)
39585+
{
39586+
// we need more than the regions we have - nothing to decommit
39587+
continue;
39588+
}
39589+
39590+
// we may have too much committed - let's see if we can decommit in the tail region
39591+
ptrdiff_t tail_region_size = heap_segment_reserved (tail_region) - heap_segment_mem (tail_region);
39592+
ptrdiff_t unneeded_tail_size = min (-budget_gen, tail_region_size);
39593+
uint8_t *decommit_target = heap_segment_reserved (tail_region) - unneeded_tail_size;
39594+
decommit_target = max (decommit_target, heap_segment_allocated (tail_region));
39595+
39596+
if (decommit_target < previous_decommit_target)
39597+
{
39598+
// we used to have a higher target - do exponential smoothing by computing
39599+
// essentially decommit_target = 1/3*decommit_target + 2/3*previous_decommit_target
39600+
// computation below is slightly different to avoid overflow
39601+
ptrdiff_t target_decrease = previous_decommit_target - decommit_target;
39602+
decommit_target += target_decrease * 2 / 3;
39603+
}
39604+
39605+
//#define STRESS_DECOMMIT 1
39606+
#ifdef STRESS_DECOMMIT
39607+
// our decommit logic should work for a random decommit target within tail_region - make sure it does
39608+
decommit_target = heap_segment_mem (tail_region) + gc_rand::get_rand (heap_segment_reserved (tail_region) - heap_segment_mem (tail_region));
39609+
#endif //STRESS_DECOMMIT
39610+
39611+
heap_segment_decommit_target (tail_region) = decommit_target;
39612+
39613+
if (decommit_target < heap_segment_committed (tail_region))
39614+
{
39615+
gradual_decommit_in_progress_p = TRUE;
39616+
39617+
dprintf (1, ("h%2d gen %d reduce_commit by %IdkB",
39618+
heap_number,
39619+
gen_number,
39620+
(heap_segment_committed (tail_region) - decommit_target)/1024));
39621+
}
39622+
dprintf(3, ("h%2d gen %d allocated: %IdkB committed: %IdkB target: %IdkB",
39623+
heap_number,
39624+
gen_number,
39625+
(heap_segment_allocated (tail_region) - heap_segment_mem (tail_region))/1024,
39626+
(heap_segment_committed (tail_region) - heap_segment_mem (tail_region))/1024,
39627+
(decommit_target - heap_segment_mem (tail_region))/1024));
39628+
}
3957839629
#else //MULTIPLE_HEAPS && USE_REGIONS
3957939630

3958039631
dynamic_data* dd0 = dynamic_data_of (0);
3958139632

3958239633
ptrdiff_t desired_allocation = dd_new_allocation (dd0) +
39583-
estimate_gen_growth (soh_gen1) +
39634+
max (estimate_gen_growth (soh_gen1), 0) +
3958439635
loh_size_threshold;
3958539636

3958639637
size_t slack_space =
@@ -39687,7 +39738,11 @@ bool gc_heap::decommit_step ()
3968739738
}
3968839739
}
3968939740
}
39690-
#else //USE_REGIONS
39741+
if (use_large_pages_p)
39742+
{
39743+
return (decommit_size != 0);
39744+
}
39745+
#endif //USE_REGIONS
3969139746
#ifdef MULTIPLE_HEAPS
3969239747
// should never get here for large pages because decommit_ephemeral_segment_pages
3969339748
// will not do anything if use_large_pages_p is true
@@ -39699,46 +39754,93 @@ bool gc_heap::decommit_step ()
3969939754
decommit_size += hp->decommit_ephemeral_segment_pages_step ();
3970039755
}
3970139756
#endif //MULTIPLE_HEAPS
39702-
#endif //USE_REGIONS
3970339757
return (decommit_size != 0);
3970439758
}
3970539759

3970639760
#ifdef MULTIPLE_HEAPS
3970739761
// return the decommitted size
39708-
#ifndef USE_REGIONS
3970939762
size_t gc_heap::decommit_ephemeral_segment_pages_step ()
3971039763
{
39711-
// we rely on desired allocation not being changed outside of GC
39712-
assert (ephemeral_heap_segment->saved_desired_allocation == dd_desired_allocation (dynamic_data_of (0)));
39713-
39714-
uint8_t* decommit_target = heap_segment_decommit_target (ephemeral_heap_segment);
39715-
size_t EXTRA_SPACE = 2 * OS_PAGE_SIZE;
39716-
decommit_target += EXTRA_SPACE;
39717-
uint8_t* committed = heap_segment_committed (ephemeral_heap_segment);
39718-
if (decommit_target < committed)
39764+
size_t size = 0;
39765+
#ifdef USE_REGIONS
39766+
for (int gen_number = soh_gen0; gen_number <= soh_gen1; gen_number++)
39767+
{
39768+
generation* gen = generation_of (gen_number);
39769+
heap_segment* seg = generation_tail_region (gen);
39770+
#else // USE_REGIONS
3971939771
{
39720-
// we rely on other threads not messing with committed if we are about to trim it down
39721-
assert (ephemeral_heap_segment->saved_committed == heap_segment_committed (ephemeral_heap_segment));
39772+
heap_segment* seg = ephemeral_heap_segment;
39773+
// we rely on desired allocation not being changed outside of GC
39774+
assert (seg->saved_desired_allocation == dd_desired_allocation (dynamic_data_of (0)));
39775+
#endif // USE_REGIONS
3972239776

39723-
// how much would we need to decommit to get to decommit_target in one step?
39724-
size_t full_decommit_size = (committed - decommit_target);
39777+
uint8_t* decommit_target = heap_segment_decommit_target (seg);
39778+
size_t EXTRA_SPACE = 2 * OS_PAGE_SIZE;
39779+
decommit_target += EXTRA_SPACE;
39780+
#ifdef STRESS_DECOMMIT
39781+
// our decommit logic should work for a random decommit target within tail_region - make sure it does
39782+
// tail region now may be different from what decommit_ephemeral_segment_pages saw
39783+
decommit_target = heap_segment_mem (seg) + gc_rand::get_rand (heap_segment_reserved (seg) - heap_segment_mem (seg));
39784+
#endif //STRESS_DECOMMIT
39785+
uint8_t* committed = heap_segment_committed (seg);
39786+
uint8_t* allocated = (seg == ephemeral_heap_segment) ? alloc_allocated : heap_segment_allocated (seg);
39787+
if ((allocated <= decommit_target) && (decommit_target < committed))
39788+
{
39789+
#ifdef USE_REGIONS
39790+
if (gen_number == soh_gen0)
39791+
{
39792+
// for gen 0, sync with the allocator by taking the more space lock
39793+
// and re-read the variables
39794+
//
39795+
// we call try_enter_spin_lock here instead of enter_spin_lock because
39796+
// calling enter_spin_lock from this thread can deadlock at the start
39797+
// of a GC - if gc_started is already true, we call wait_for_gc_done(),
39798+
// but we are on GC thread 0, so GC cannot make progress
39799+
if (!try_enter_spin_lock (&more_space_lock_soh))
39800+
{
39801+
continue;
39802+
}
39803+
add_saved_spinlock_info (false, me_acquire, mt_decommit_step);
39804+
seg = generation_tail_region (gen);
39805+
#ifndef STRESS_DECOMMIT
39806+
decommit_target = heap_segment_decommit_target (seg);
39807+
decommit_target += EXTRA_SPACE;
39808+
#endif
39809+
committed = heap_segment_committed (seg);
39810+
allocated = (seg == ephemeral_heap_segment) ? alloc_allocated : heap_segment_allocated (seg);
39811+
}
39812+
if ((allocated <= decommit_target) && (decommit_target < committed))
39813+
#else // USE_REGIONS
39814+
// we rely on other threads not messing with committed if we are about to trim it down
39815+
assert (seg->saved_committed == heap_segment_committed (seg));
39816+
#endif // USE_REGIONS
39817+
{
39818+
// how much would we need to decommit to get to decommit_target in one step?
39819+
size_t full_decommit_size = (committed - decommit_target);
3972539820

39726-
// don't do more than max_decommit_step_size per step
39727-
size_t decommit_size = min (max_decommit_step_size, full_decommit_size);
39821+
// don't do more than max_decommit_step_size per step
39822+
size_t decommit_size = min (max_decommit_step_size, full_decommit_size);
3972839823

39729-
// figure out where the new committed should be
39730-
uint8_t* new_committed = (committed - decommit_size);
39731-
size_t size = decommit_heap_segment_pages_worker (ephemeral_heap_segment, new_committed);
39824+
// figure out where the new committed should be
39825+
uint8_t* new_committed = (committed - decommit_size);
39826+
size += decommit_heap_segment_pages_worker (seg, new_committed);
3973239827

3973339828
#ifdef _DEBUG
39734-
ephemeral_heap_segment->saved_committed = committed - size;
39829+
seg->saved_committed = committed - size;
3973539830
#endif // _DEBUG
39736-
39737-
return size;
39831+
}
39832+
#ifdef USE_REGIONS
39833+
if (gen_number == soh_gen0)
39834+
{
39835+
// for gen 0, we took the more space lock - leave it again
39836+
add_saved_spinlock_info (false, me_release, mt_decommit_step);
39837+
leave_spin_lock (&more_space_lock_soh);
39838+
}
39839+
#endif // USE_REGIONS
39840+
}
3973839841
}
39739-
return 0;
39842+
return size;
3974039843
}
39741-
#endif //!USE_REGIONS
3974239844
#endif //MULTIPLE_HEAPS
3974339845

3974439846
//This is meant to be called by decide_on_compacting.

src/coreclr/gc/gcpriv.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,7 +1036,8 @@ enum msl_take_state
10361036
mt_alloc_large_cant,
10371037
mt_try_alloc,
10381038
mt_try_budget,
1039-
mt_try_servo_budget
1039+
mt_try_servo_budget,
1040+
mt_decommit_step
10401041
};
10411042

10421043
enum msl_enter_state
@@ -2012,10 +2013,10 @@ class gc_heap
20122013
void reset_heap_segment_pages (heap_segment* seg);
20132014
PER_HEAP
20142015
void decommit_heap_segment_pages (heap_segment* seg, size_t extra_space);
2015-
#if defined(MULTIPLE_HEAPS) && !defined(USE_REGIONS)
2016+
#if defined(MULTIPLE_HEAPS)
20162017
PER_HEAP
20172018
size_t decommit_ephemeral_segment_pages_step ();
2018-
#endif //MULTIPLE_HEAPS && !USE_REGIONS
2019+
#endif //MULTIPLE_HEAPS
20192020
PER_HEAP
20202021
size_t decommit_heap_segment_pages_worker (heap_segment* seg, uint8_t *new_committed);
20212022
PER_HEAP_ISOLATED
@@ -5596,9 +5597,7 @@ class heap_segment
55965597
size_t saved_desired_allocation;
55975598
#endif // _DEBUG
55985599
#endif //MULTIPLE_HEAPS
5599-
#if !defined(MULTIPLE_HEAPS) || !defined(USE_REGIONS)
56005600
uint8_t* decommit_target;
5601-
#endif //!MULTIPLE_HEAPS || !USE_REGIONS
56025601
uint8_t* plan_allocated;
56035602
// In the plan phase we change the allocated for a seg but we need this
56045603
// value to correctly calculate how much space we can reclaim in
@@ -5897,13 +5896,11 @@ uint8_t*& heap_segment_committed (heap_segment* inst)
58975896
{
58985897
return inst->committed;
58995898
}
5900-
#if !defined(MULTIPLE_HEAPS) || !defined(USE_REGIONS)
59015899
inline
59025900
uint8_t*& heap_segment_decommit_target (heap_segment* inst)
59035901
{
59045902
return inst->decommit_target;
59055903
}
5906-
#endif //!MULTIPLE_HEAPS || !USE_REGIONS
59075904
inline
59085905
uint8_t*& heap_segment_used (heap_segment* inst)
59095906
{

0 commit comments

Comments
 (0)