diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index d4d1f44d2d5f5..ec56c4d55e29f 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -1988,6 +1988,9 @@ const int max_snoop_level = 128; // time in milliseconds between decommit steps #define DECOMMIT_TIME_STEP_MILLISECONDS (100) +// arbitrary - 10 GCs worth of decommitting +#define MAX_DECOMMIT_SIZE (10 * DECOMMIT_SIZE_PER_MILLISECOND * DECOMMIT_TIME_STEP_MILLISECONDS) + inline size_t align_on_page (size_t add) { @@ -2807,6 +2810,7 @@ uint64_t gc_heap::total_uoh_a_last_bgc = 0; #ifdef USE_REGIONS region_free_list gc_heap::global_regions_to_decommit[count_free_region_kinds]; +size_t gc_heap::to_decommit_size_last_distribute = 0; region_free_list gc_heap::global_free_huge_regions; #else //USE_REGIONS size_t gc_heap::eph_gen_starts_size = 0; @@ -13264,7 +13268,7 @@ void gc_heap::age_free_regions (const char* msg) void gc_heap::distribute_free_regions() { - const int kind_count = large_free_region + 1; + const int kind_count = count_core_free_region_kinds; #ifdef MULTIPLE_HEAPS BOOL joined_last_gc_before_oom = FALSE; @@ -13299,6 +13303,8 @@ void gc_heap::distribute_free_regions() while (decommit_step(DECOMMIT_TIME_STEP_MILLISECONDS)) { } + to_decommit_size_last_distribute = 0; + #ifdef MULTIPLE_HEAPS for (int i = 0; i < n_heaps; i++) { @@ -13343,10 +13349,12 @@ void gc_heap::distribute_free_regions() size_t min_heap_budget_in_region_units[MAX_SUPPORTED_CPUS]; size_t region_size[kind_count] = { global_region_allocator.get_region_alignment(), global_region_allocator.get_large_region_alignment() }; region_free_list surplus_regions[kind_count]; + size_t to_decommit_size_start_distribute = 0; for (int kind = basic_free_region; kind < kind_count; kind++) { - // we may still have regions left on the regions_to_decommit list - + // we may still have regions left on the global_regions_to_decommit list - // use these to fill the budget as well + to_decommit_size_start_distribute += global_regions_to_decommit[kind].get_size_free_regions(); surplus_regions[kind].transfer_regions (&global_regions_to_decommit[kind]); } #ifdef MULTIPLE_HEAPS @@ -13433,6 +13441,20 @@ void gc_heap::distribute_free_regions() dprintf (1, ("moved %2zd regions (%8zd) to decommit based on time", num_decommit_regions_by_time, size_decommit_regions_by_time)); + to_decommit_size_start_distribute += global_regions_to_decommit[huge_free_region].get_size_free_regions(); + + ptrdiff_t decommit_budget = MAX_DECOMMIT_SIZE; + // If not all entries on the global_regions_to_decommit list were cleared since that last distribute, then don't put more + // on that list than were cleared in the last iteration (with some buffer). + if (to_decommit_size_start_distribute > 0) + { + ptrdiff_t decommit_budget_from_history = to_decommit_size_last_distribute - to_decommit_size_start_distribute; + // Add arbitrary buffer + decommit_budget_from_history = (3 * decommit_budget_from_history) / 2; + // Absolute limit on the number of regions to decommit + decommit_budget = min (decommit_budget, decommit_budget_from_history); + } + global_free_huge_regions.transfer_regions (&global_regions_to_decommit[huge_free_region]); size_t free_space_in_huge_regions = global_free_huge_regions.get_size_free_regions(); @@ -13476,18 +13498,36 @@ void gc_heap::distribute_free_regions() #endif ) { - // ignore young huge regions when determining how much to decommit - num_regions_to_decommit[kind] = - max(static_cast(0), - (balance - static_cast(num_young_huge_region_units_to_consider[kind]))); - - balance -= num_regions_to_decommit[kind]; + num_regions_to_decommit[kind] = balance; dprintf(REGIONS_LOG, ("distributing the %zd %s regions, removing %zd regions", total_budget_in_region_units[kind], kind_name[kind], num_regions_to_decommit[kind])); + // ignore young huge regions when determining how much to decommit + if (num_young_huge_region_units_to_consider[kind] > 0) + { + num_regions_to_decommit[kind] = + max(static_cast(0), + (num_regions_to_decommit[kind] - static_cast(num_young_huge_region_units_to_consider[kind]))); + dprintf(REGIONS_LOG, ("ignoring %zd young huge region units, removing %zd region units", + num_young_huge_region_units_to_consider[kind], + num_regions_to_decommit[kind])); + } + + // also limit to budget + ptrdiff_t decommit_budget_units = decommit_budget / region_size[kind]; + if (num_regions_to_decommit[kind] > decommit_budget_units) + { + num_regions_to_decommit[kind] = decommit_budget_units; + dprintf(REGIONS_LOG, ("limiting removal to %zd region units due to budget", + num_regions_to_decommit[kind])); + } + + decommit_budget -= num_regions_to_decommit[kind] * region_size[kind]; + balance -= num_regions_to_decommit[kind]; + if (num_regions_to_decommit[kind] > 0) { // remember how many regions we had on the decommit list already due to aging @@ -13658,6 +13698,13 @@ void gc_heap::distribute_free_regions() } } #endif //MULTIPLE_HEAPS + + // Record the global_regions_to_decommit sizes for the next redistribute + to_decommit_size_last_distribute = 0; + for (int kind = basic_free_region; kind < count_free_region_kinds; kind++) + { + to_decommit_size_last_distribute += global_regions_to_decommit[kind].get_size_free_regions(); + } } #endif //USE_REGIONS diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 660ca2dad38ae..f431e174e63a4 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -1373,6 +1373,10 @@ enum free_region_kind large_free_region, huge_free_region, count_free_region_kinds, + + // Many calculations consider huge regions to be a number of large region units. + // This count excludes huge regions for those cases. + count_core_free_region_kinds = large_free_region + 1, }; static_assert(count_free_region_kinds == FREE_REGION_KINDS, "Keep count_free_region_kinds in sync with FREE_REGION_KINDS, changing this is not a version breaking change."); @@ -4263,6 +4267,9 @@ class gc_heap PER_HEAP_ISOLATED_FIELD_MAINTAINED uint8_t*** g_mark_list_piece; PER_HEAP_ISOLATED_FIELD_MAINTAINED region_free_list global_regions_to_decommit[count_free_region_kinds]; + // At the end of each distribute_free_regions call, we record the size of global_regions_to_decommit so that + // we can see the decommit progress during the next call. + PER_HEAP_ISOLATED_FIELD_MAINTAINED size_t to_decommit_size_last_distribute; PER_HEAP_ISOLATED_FIELD_MAINTAINED region_free_list global_free_huge_regions; #else //USE_REGIONS