From ade5076808617e28de3f2355d8d8052b9f061bd0 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Tue, 3 Aug 2021 12:49:17 +0200 Subject: [PATCH 1/6] Cleaned up version of earlier PR #48601. --- src/coreclr/gc/gc.cpp | 118 ++++++++++++++++++++++++++++++-------- src/coreclr/gc/gcconfig.h | 1 + src/coreclr/gc/gcpriv.h | 5 ++ 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 9c8acf346ff42..ceb23c7f70e1f 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -2243,6 +2243,7 @@ double gc_heap::short_plugs_pad_ratio = 0; #endif //SHORT_PLUGS int gc_heap::generation_skip_ratio_threshold = 0; +int gc_heap::conserve_mem_setting = 0; uint64_t gc_heap::suspended_start_time = 0; uint64_t gc_heap::end_gc_time = 0; @@ -3113,17 +3114,20 @@ gc_heap::dt_high_frag_p (gc_tuning_point tp, } else { -#ifndef MULTIPLE_HEAPS if (gen_number == max_generation) { float frag_ratio = (float)(dd_fragmentation (dynamic_data_of (max_generation))) / (float)generation_size (max_generation); - if (frag_ratio > 0.65) + float frag_limit = 1.0f - conserve_mem_setting / 10.0f; +#ifndef MULTIPLE_HEAPS + if (conserve_mem_setting == 0) + frag_limit = 0.65f; +#endif //!MULTIPLE_HEAPS + if (frag_ratio > frag_limit) { dprintf (GTC_LOG, ("g2 FR: %d%%", (int)(frag_ratio*100))); return TRUE; } } -#endif //!MULTIPLE_HEAPS size_t fr = generation_unusable_fragmentation (generation_of (gen_number)); ret = (fr > dd_fragmentation_limit(dd)); if (ret) @@ -11858,9 +11862,9 @@ void gc_heap::make_generation (int gen_num, heap_segment* seg, uint8_t* start) #endif //DOUBLY_LINKED_FL #ifdef FREE_USAGE_STATS - memset (gen->gen_free_spaces, 0, sizeof (gen.gen_free_spaces)); - memset (gen->gen_current_pinned_free_spaces, 0, sizeof (gen.gen_current_pinned_free_spaces)); - memset (gen->gen_plugs, 0, sizeof (gen.gen_plugs)); + memset (gen->gen_free_spaces, 0, sizeof (gen->gen_free_spaces)); + memset (gen->gen_current_pinned_free_spaces, 0, sizeof (gen->gen_current_pinned_free_spaces)); + memset (gen->gen_plugs, 0, sizeof (gen->gen_plugs)); #endif //FREE_USAGE_STATS } @@ -12455,6 +12459,14 @@ gc_heap::init_semi_shared() #endif //FEATURE_LOH_COMPACTION #endif //FEATURE_EVENT_TRACE + conserve_mem_setting = (int)GCConfig::GetGCConserveMem(); + if (conserve_mem_setting < 0) + conserve_mem_setting = 0; + if (conserve_mem_setting > 9) + conserve_mem_setting = 9; + + dprintf (1, ("conserve_mem_setting = %d", conserve_mem_setting)); + ret = 1; cleanup: @@ -16961,7 +16973,6 @@ void gc_heap::init_free_and_plug() #else memset (gen->gen_free_spaces, 0, sizeof (gen->gen_free_spaces)); #endif //DOUBLY_LINKED_FL - memset (gen->gen_plugs_allocated_in_free, 0, sizeof (gen->gen_plugs_allocated_in_free)); memset (gen->gen_plugs, 0, sizeof (gen->gen_plugs)); memset (gen->gen_current_pinned_free_spaces, 0, sizeof (gen->gen_current_pinned_free_spaces)); } @@ -16979,7 +16990,7 @@ void gc_heap::init_free_and_plug() void gc_heap::print_free_and_plug (const char* msg) { -#if defined(FREE_USAGE_STATS) && defined(SIMPLE_DPRINTF) +#ifdef FREE_USAGE_STATS int older_gen = ((settings.condemned_generation == max_generation) ? max_generation : (settings.condemned_generation + 1)); for (int i = 0; i <= older_gen; i++) { @@ -17000,7 +17011,7 @@ void gc_heap::print_free_and_plug (const char* msg) } #else UNREFERENCED_PARAMETER(msg); -#endif //FREE_USAGE_STATS && SIMPLE_DPRINTF +#endif //FREE_USAGE_STATS } // replace with allocator::first_suitable_bucket @@ -17072,8 +17083,8 @@ void gc_heap::add_gen_free (int gen_number, size_t free_size) (gen->gen_free_spaces[i])++; if (gen_number == max_generation) { - dprintf (3, ("Mb b%d: f+ %Id (%Id->%Id)", - i, free_size, (gen->gen_free_spaces[i]).num_items, (gen->gen_free_spaces[i]).total_size)); + dprintf (3, ("Mb b%d: f+ %Id (%Id)", + i, free_size, gen->gen_free_spaces[i])); } #else UNREFERENCED_PARAMETER(gen_number); @@ -17095,8 +17106,8 @@ void gc_heap::remove_gen_free (int gen_number, size_t free_size) (gen->gen_free_spaces[i])--; if (gen_number == max_generation) { - dprintf (3, ("Mb b%d: f- %Id (%Id->%Id)", - i, free_size, (gen->gen_free_spaces[i]).num_items, (gen->gen_free_spaces[i]).total_size)); + dprintf (3, ("Mb b%d: f- %Id (%Id)", + i, free_size, gen->gen_free_spaces[i])); } #else UNREFERENCED_PARAMETER(gen_number); @@ -18257,26 +18268,28 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation, } else if ((current_total_committed * 10) >= (heap_hard_limit * 9)) { - size_t loh_frag = get_total_gen_fragmentation (loh_generation); + size_t combined_frag = get_total_gen_fragmentation (max_generation) + + get_total_gen_fragmentation (loh_generation); - // If the LOH frag is >= 1/8 it's worth compacting it - if ((loh_frag * 8) >= heap_hard_limit) + // If the combined frag is >= 1/8 it's worth compacting + if ((combined_frag * 8) >= heap_hard_limit) { - dprintf (GTC_LOG, ("loh frag: %Id > 1/8 of limit %Id", loh_frag, (heap_hard_limit / 8))); + dprintf (GTC_LOG, ("gen2+loh frag: %Id > 1/8 of limit %Id", combined_frag, (heap_hard_limit / 8))); gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_loh_frag); full_compact_gc_p = true; } else { // If there's not much fragmentation but it looks like it'll be productive to - // collect LOH, do that. - size_t est_loh_reclaim = get_total_gen_estimated_reclaim (loh_generation); - if ((est_loh_reclaim * 8) >= heap_hard_limit) + // collect, do that. + size_t est_combined_reclaim = get_total_gen_estimated_reclaim (max_generation) + + get_total_gen_estimated_reclaim (loh_generation); + if ((est_combined_reclaim * 8) >= heap_hard_limit) { - gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_loh_reclaim); + gc_data_global.gen_to_condemn_reasons.set_condition (gen_joined_limit_loh_reclaim); full_compact_gc_p = true; } - dprintf (GTC_LOG, ("loh est reclaim: %Id, 1/8 of limit %Id", est_loh_reclaim, (heap_hard_limit / 8))); + dprintf (GTC_LOG, ("gen2+loh est reclaim: %Id, 1/8 of limit %Id", est_combined_reclaim, (heap_hard_limit / 8))); } } @@ -18285,7 +18298,38 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation, n = max_generation; *blocking_collection_p = TRUE; settings.loh_compaction = TRUE; - dprintf (GTC_LOG, ("compacting LOH due to hard limit")); + dprintf (GTC_LOG, ("compacting gen2+loh due to hard limit")); + } + } + + if ((conserve_mem_setting != 0) && (n >= max_generation)) + { + float frag_limit = 1.0f - conserve_mem_setting / 10.0f; + + size_t loh_size = get_total_gen_size (loh_generation); + size_t gen2_size = get_total_gen_size (max_generation); + float loh_frag_ratio = 0.0f; + float combined_frag_ratio = 0.0f; + if (loh_size != 0) + { + size_t loh_frag = get_total_gen_fragmentation (loh_generation); + size_t gen2_frag = get_total_gen_fragmentation (max_generation); + loh_frag_ratio = (float)loh_frag / (float)loh_size; + combined_frag_ratio = (float)(gen2_frag + loh_frag) / (float)(gen2_size + loh_size); + } + if (combined_frag_ratio > frag_limit) + { + dprintf (GTC_LOG, ("combined frag: %f > limit %f, loh frag: %f", combined_frag_ratio, frag_limit, loh_frag_ratio)); + gc_data_global.gen_to_condemn_reasons.set_condition (gen_max_high_frag_p); + + n = max_generation; + *blocking_collection_p = TRUE; + if (loh_frag_ratio > frag_limit) + { + settings.loh_compaction = TRUE; + + dprintf (GTC_LOG, ("compacting LOH due to GCConserveMem setting")); + } } } @@ -23360,6 +23404,21 @@ size_t gc_heap::get_total_gen_estimated_reclaim (int gen_number) return total_estimated_reclaim; } +size_t gc_heap::get_total_gen_size (int gen_number) +{ +#ifdef MULTIPLE_HEAPS + size_t size = 0; + for (int hn = 0; hn < gc_heap::n_heaps; hn++) + { + gc_heap* hp = gc_heap::g_heaps[hn]; + size += hp->generation_sizes (hp->generation_of (gen_number)); + } +#else + size_t size = generation_sizes (generation_of (gen_number)); +#endif //MULTIPLE_HEAPS + return size; +} + size_t gc_heap::committed_size() { size_t total_committed = 0; @@ -25521,7 +25580,7 @@ BOOL gc_heap::plan_loh() void gc_heap::compact_loh() { - assert (loh_compaction_requested() || heap_hard_limit); + assert (loh_compaction_requested() || heap_hard_limit || conserve_mem_setting); #ifdef FEATURE_EVENT_TRACE uint64_t start_time, end_time; @@ -37294,6 +37353,16 @@ size_t gc_heap::desired_new_allocation (dynamic_data* dd, cst = min (1.0f, float (out) / float (dd_begin_data_size (dd))); f = surv_to_growth (cst, limit, max_limit); + if (conserve_mem_setting != 0) + { + // if this is set, compute a growth factor based on it. + // formula below means use 50% of the allowable fragmentation + float f_conserve = (10.0f / conserve_mem_setting - 1) * 0.5f + 1.0f; + + // use the smaller one + f = min (f, f_conserve); + } + size_t max_growth_size = (size_t)(max_size / f); if (current_size >= max_growth_size) { @@ -37317,6 +37386,7 @@ size_t gc_heap::desired_new_allocation (dynamic_data* dd, #ifdef BGC_SERVO_TUNING !bgc_tuning::fl_tuning_triggered && #endif //BGC_SERVO_TUNING + (conserve_mem_setting == 0) && (dd_fragmentation (dd) > ((size_t)((f-1)*current_size)))) { //reducing allocation in case of fragmentation diff --git a/src/coreclr/gc/gcconfig.h b/src/coreclr/gc/gcconfig.h index 8a9112ec48084..886ea17743880 100644 --- a/src/coreclr/gc/gcconfig.h +++ b/src/coreclr/gc/gcconfig.h @@ -133,6 +133,7 @@ class GCConfigStringHolder INT_CONFIG (GCHeapHardLimitLOHPercent, "GCHeapHardLimitLOHPercent", "System.GC.HeapHardLimitLOHPercent", 0, "Specifies the GC heap LOH usage as a percentage of the total memory") \ INT_CONFIG (GCHeapHardLimitPOHPercent, "GCHeapHardLimitPOHPercent", "System.GC.HeapHardLimitPOHPercent", 0, "Specifies the GC heap POH usage as a percentage of the total memory") \ INT_CONFIG (GCEnabledInstructionSets, "GCEnabledInstructionSets", NULL, -1, "Specifies whether GC can use AVX2 or AVX512F - 0 for neither, 1 for AVX2, 3 for AVX512F")\ + INT_CONFIG (GCConserveMem, "GCConserveMemory", NULL, 0, "Specifies how hard GC should try to conserve memory - values 0-9") \ // This class is responsible for retreiving configuration information // for how the GC should operate. diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index bbc7e055f2661..327f7481757fb 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -3142,6 +3142,8 @@ class gc_heap PER_HEAP_ISOLATED size_t get_total_gen_estimated_reclaim (int gen_number); PER_HEAP_ISOLATED + size_t get_total_gen_size (int gen_number); + PER_HEAP_ISOLATED void get_memory_info (uint32_t* memory_load, uint64_t* available_physical=NULL, uint64_t* available_page_file=NULL); @@ -4639,6 +4641,9 @@ class gc_heap PER_HEAP_ISOLATED int generation_skip_ratio_threshold; + PER_HEAP_ISOLATED + int conserve_mem_setting; + PER_HEAP BOOL gen0_bricks_cleared; PER_HEAP From 6a102b35cd386ba18f7d4555696f470c3159f305 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Wed, 4 Aug 2021 11:13:55 +0200 Subject: [PATCH 2/6] Address code review feedback. --- src/coreclr/gc/gc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index ceb23c7f70e1f..55f68abc4f1fe 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -18302,7 +18302,7 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation, } } - if ((conserve_mem_setting != 0) && (n >= max_generation)) + if ((conserve_mem_setting != 0) && (n == max_generation)) { float frag_limit = 1.0f - conserve_mem_setting / 10.0f; @@ -23411,7 +23411,7 @@ size_t gc_heap::get_total_gen_size (int gen_number) for (int hn = 0; hn < gc_heap::n_heaps; hn++) { gc_heap* hp = gc_heap::g_heaps[hn]; - size += hp->generation_sizes (hp->generation_of (gen_number)); + size += hp->generation_size (hp->generation_of (gen_number)); } #else size_t size = generation_sizes (generation_of (gen_number)); From 03a0d295771f7adc9a26ac9761f9781db02efadb Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Wed, 4 Aug 2021 11:24:08 +0200 Subject: [PATCH 3/6] Take out changes in dt_high_frag_p - probably no longer necessary. Remove changes for the heap_hard_limit case - we'll do those later. Fixed another place where I had called generation_sizes instead of generation_size. --- src/coreclr/gc/gc.cpp | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 55f68abc4f1fe..eb984204088e1 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -3114,20 +3114,17 @@ gc_heap::dt_high_frag_p (gc_tuning_point tp, } else { +#ifndef MULTIPLE_HEAPS if (gen_number == max_generation) { float frag_ratio = (float)(dd_fragmentation (dynamic_data_of (max_generation))) / (float)generation_size (max_generation); - float frag_limit = 1.0f - conserve_mem_setting / 10.0f; -#ifndef MULTIPLE_HEAPS - if (conserve_mem_setting == 0) - frag_limit = 0.65f; -#endif //!MULTIPLE_HEAPS - if (frag_ratio > frag_limit) + if (frag_ratio > 0.65) { dprintf (GTC_LOG, ("g2 FR: %d%%", (int)(frag_ratio*100))); return TRUE; } } +#endif //!MULTIPLE_HEAPS size_t fr = generation_unusable_fragmentation (generation_of (gen_number)); ret = (fr > dd_fragmentation_limit(dd)); if (ret) @@ -18268,28 +18265,26 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation, } else if ((current_total_committed * 10) >= (heap_hard_limit * 9)) { - size_t combined_frag = get_total_gen_fragmentation (max_generation) + - get_total_gen_fragmentation (loh_generation); + size_t loh_frag = get_total_gen_fragmentation (loh_generation); - // If the combined frag is >= 1/8 it's worth compacting - if ((combined_frag * 8) >= heap_hard_limit) + // If the LOH frag is >= 1/8 it's worth compacting it + if ((loh_frag * 8) >= heap_hard_limit) { - dprintf (GTC_LOG, ("gen2+loh frag: %Id > 1/8 of limit %Id", combined_frag, (heap_hard_limit / 8))); + dprintf (GTC_LOG, ("loh frag: %Id > 1/8 of limit %Id", loh_frag, (heap_hard_limit / 8))); gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_loh_frag); full_compact_gc_p = true; } else { // If there's not much fragmentation but it looks like it'll be productive to - // collect, do that. - size_t est_combined_reclaim = get_total_gen_estimated_reclaim (max_generation) + - get_total_gen_estimated_reclaim (loh_generation); - if ((est_combined_reclaim * 8) >= heap_hard_limit) + // collect LOH, do that. + size_t est_loh_reclaim = get_total_gen_estimated_reclaim (loh_generation); + if ((est_loh_reclaim * 8) >= heap_hard_limit) { - gc_data_global.gen_to_condemn_reasons.set_condition (gen_joined_limit_loh_reclaim); + gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_loh_reclaim); full_compact_gc_p = true; } - dprintf (GTC_LOG, ("gen2+loh est reclaim: %Id, 1/8 of limit %Id", est_combined_reclaim, (heap_hard_limit / 8))); + dprintf (GTC_LOG, ("loh est reclaim: %Id, 1/8 of limit %Id", est_loh_reclaim, (heap_hard_limit / 8))); } } @@ -18298,7 +18293,7 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation, n = max_generation; *blocking_collection_p = TRUE; settings.loh_compaction = TRUE; - dprintf (GTC_LOG, ("compacting gen2+loh due to hard limit")); + dprintf (GTC_LOG, ("compacting LOH due to hard limit")); } } @@ -23414,7 +23409,7 @@ size_t gc_heap::get_total_gen_size (int gen_number) size += hp->generation_size (hp->generation_of (gen_number)); } #else - size_t size = generation_sizes (generation_of (gen_number)); + size_t size = generation_size (generation_of (gen_number)); #endif //MULTIPLE_HEAPS return size; } From e9706a6d57ab864e4b4707ab96d58143341e8757 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Wed, 4 Aug 2021 11:28:15 +0200 Subject: [PATCH 4/6] Fixed parameter for call to generation_size. --- src/coreclr/gc/gc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index eb984204088e1..77a822829d249 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -23406,10 +23406,10 @@ size_t gc_heap::get_total_gen_size (int gen_number) for (int hn = 0; hn < gc_heap::n_heaps; hn++) { gc_heap* hp = gc_heap::g_heaps[hn]; - size += hp->generation_size (hp->generation_of (gen_number)); + size += hp->generation_size (gen_number); } #else - size_t size = generation_size (generation_of (gen_number)); + size_t size = generation_size (gen_number); #endif //MULTIPLE_HEAPS return size; } From 7150c72a76eadafafc208ce291946a7508085fd2 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Wed, 4 Aug 2021 12:13:45 +0200 Subject: [PATCH 5/6] Update comment to the more explicit version in .NET Framework. --- src/coreclr/gc/gc.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 77a822829d249..46a4fefcd771b 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -37351,7 +37351,10 @@ size_t gc_heap::desired_new_allocation (dynamic_data* dd, if (conserve_mem_setting != 0) { // if this is set, compute a growth factor based on it. - // formula below means use 50% of the allowable fragmentation + // example: a setting of 6 means we have a goal of 60% live data + // this means we allow 40% fragmentation + // to keep heap size stable, we only use half of that (20%) for new allocation + // f is (live data + new allocation)/(live data), so would be (60% + 20%) / 60% or 1.33 float f_conserve = (10.0f / conserve_mem_setting - 1) * 0.5f + 1.0f; // use the smaller one From b0131a6b826f4d85b4c176250b5aca13c9f0d690 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Fri, 6 Aug 2021 13:19:34 +0200 Subject: [PATCH 6/6] Added parentheses around an expression to address code review feedback. --- src/coreclr/gc/gc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 46a4fefcd771b..05e8be5e95391 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -37355,7 +37355,7 @@ size_t gc_heap::desired_new_allocation (dynamic_data* dd, // this means we allow 40% fragmentation // to keep heap size stable, we only use half of that (20%) for new allocation // f is (live data + new allocation)/(live data), so would be (60% + 20%) / 60% or 1.33 - float f_conserve = (10.0f / conserve_mem_setting - 1) * 0.5f + 1.0f; + float f_conserve = ((10.0f / conserve_mem_setting) - 1) * 0.5f + 1.0f; // use the smaller one f = min (f, f_conserve);