diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 9c8acf346ff424..033b5b8ec0e76c 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); @@ -17206,6 +17217,8 @@ uint8_t* gc_heap::allocate_in_older_generation (generation* gen, size_t size, for (unsigned int a_l_idx = gen_allocator->first_suitable_bucket(real_size * 2); a_l_idx < gen_allocator->number_of_buckets(); a_l_idx++) { + dprintf (3, ("trying free list bucket %u for size %Id", a_l_idx, real_size)); + uint8_t* free_list = 0; uint8_t* prev_free_item = 0; @@ -18257,26 +18270,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); 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 +18300,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 +23406,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 +25582,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 +37355,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 +37388,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 @@ -37415,6 +37487,8 @@ size_t gc_heap::desired_new_allocation (dynamic_data* dd, heap_number, gen_number, out, current_size, (dd_desired_allocation (dd) - dd_gc_new_allocation (dd)), (int)(cst*100), (int)(f*100), current_size + new_allocation, new_allocation)); + dprintf (2, ("heap%d gen%d size: %Id MB fragmentation: %Id MB", heap_number, gen_number, generation_size (gen_number)/(1924*1024), dd_fragmentation (dd)/(1924*1024))); + return new_allocation_ret; } } diff --git a/src/coreclr/gc/gcconfig.h b/src/coreclr/gc/gcconfig.h index 8a9112ec480849..886ea17743880d 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 bbc7e055f2661a..fa2026c6e3defc 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -147,6 +147,8 @@ inline void FATAL_GC_ERROR() //#define SYNCHRONIZATION_STATS //#define SEG_REUSE_STATS +//#define FREE_USAGE_STATS + #ifdef SYNCHRONIZATION_STATS #define BEGIN_TIMING(x) \ int64_t x##_start; \ @@ -3142,6 +3144,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 +4643,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