diff --git a/src/coreclr/gc/env/common.h b/src/coreclr/gc/env/common.h index 78562ef0438b40..a3f6539aa3a491 100644 --- a/src/coreclr/gc/env/common.h +++ b/src/coreclr/gc/env/common.h @@ -22,6 +22,7 @@ #include #include #include +#include #include diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 0471326c0af5f7..4d281b16251a1e 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -2367,6 +2367,7 @@ int gc_heap::conserve_mem_setting = 0; bool gc_heap::spin_count_unit_config_p = false; uint64_t gc_heap::suspended_start_time = 0; +uint64_t gc_heap::change_heap_count_time = 0; uint64_t gc_heap::end_gc_time = 0; uint64_t gc_heap::total_suspended_time = 0; uint64_t gc_heap::process_start_time = 0; @@ -22015,7 +22016,7 @@ void gc_heap::update_end_gc_time_per_heap() if (heap_number == 0) { - dprintf (6666, ("prev gen%d GC end time: prev start %I64d + prev gc elapsed %Id = %I64d", + dprintf (3, ("prev gen%d GC end time: prev start %I64d + prev gc elapsed %Id = %I64d", gen_number, dd_previous_time_clock (dd), dd_gc_elapsed_time (dd), (dd_previous_time_clock (dd) + dd_gc_elapsed_time (dd)))); } @@ -22023,45 +22024,53 @@ void gc_heap::update_end_gc_time_per_heap() if (heap_number == 0) { - dprintf (6666, ("updated NGC%d %Id elapsed time to %I64d - %I64d = %I64d", gen_number, dd_gc_clock (dd), end_gc_time, dd_time_clock (dd), dd_gc_elapsed_time (dd))); + dprintf (3, ("updated NGC%d %Id elapsed time to %I64d - %I64d = %I64d", gen_number, dd_gc_clock (dd), end_gc_time, dd_time_clock (dd), dd_gc_elapsed_time (dd))); } } #ifdef DYNAMIC_HEAP_COUNT if ((heap_number == 0) && (dynamic_adaptation_mode == dynamic_adaptation_to_application_sizes)) { - dynamic_heap_count_data_t::sample& sample = dynamic_heap_count_data.samples[dynamic_heap_count_data.sample_index]; - sample.elapsed_between_gcs = end_gc_time - last_suspended_end_time; - sample.gc_pause_time = dd_gc_elapsed_time (dynamic_data_of (0)); - sample.msl_wait_time = get_msl_wait_time(); + if (settings.gc_index > 1) + { + dynamic_heap_count_data_t::sample& sample = dynamic_heap_count_data.samples[dynamic_heap_count_data.sample_index]; + sample.elapsed_between_gcs = end_gc_time - last_suspended_end_time; + sample.gc_pause_time = dd_gc_elapsed_time (dynamic_data_of (0)); + sample.msl_wait_time = get_msl_wait_time (); + // could cache this - we will get it again soon in do_post_gc + sample.gc_survived_size = get_total_promoted (); - dprintf (6666, ("sample#%d: this GC end %I64d - last sus end %I64d = %I64d, this GC pause %I64d, msl wait %I64d", - dynamic_heap_count_data.sample_index, end_gc_time, last_suspended_end_time, sample.elapsed_between_gcs, sample.gc_pause_time, sample.msl_wait_time)); + dprintf (6666, ("sample#%d: this GC end %I64d - last sus end %I64d = %I64d, this GC pause %I64d, msl wait %I64d", + dynamic_heap_count_data.sample_index, end_gc_time, last_suspended_end_time, sample.elapsed_between_gcs, sample.gc_pause_time, sample.msl_wait_time)); - last_suspended_end_time = end_gc_time; + GCEventFireHeapCountSample_V1 ( + (uint64_t)VolatileLoadWithoutBarrier (&settings.gc_index), + sample.elapsed_between_gcs, + sample.gc_pause_time, + sample.msl_wait_time); - GCEventFireHeapCountSample_V1 ( - (uint64_t)VolatileLoadWithoutBarrier (&settings.gc_index), - sample.elapsed_between_gcs, - sample.gc_pause_time, - sample.msl_wait_time); + dynamic_heap_count_data.sample_index = (dynamic_heap_count_data.sample_index + 1) % dynamic_heap_count_data_t::sample_size; + (dynamic_heap_count_data.current_samples_count)++; - dynamic_heap_count_data.sample_index = (dynamic_heap_count_data.sample_index + 1) % dynamic_heap_count_data_t::sample_size; + if (settings.condemned_generation == max_generation) + { + gc_index_full_gc_end = dd_gc_clock (dynamic_data_of (0)); + size_t elapsed_between_gen2_gcs = end_gc_time - prev_gen2_end_time; + size_t gen2_elapsed_time = sample.gc_pause_time; + dynamic_heap_count_data_t::gen2_sample& g2_sample = dynamic_heap_count_data.gen2_samples[dynamic_heap_count_data.gen2_sample_index]; + g2_sample.gc_index = VolatileLoadWithoutBarrier (&(settings.gc_index)); + g2_sample.gc_percent = (float)gen2_elapsed_time * 100.0f / elapsed_between_gen2_gcs; + (dynamic_heap_count_data.current_gen2_samples_count)++; - if (settings.condemned_generation == max_generation) - { - gc_index_full_gc_end = dd_gc_clock (dynamic_data_of (0)); - size_t elapsed_between_gen2_gcs = end_gc_time - prev_gen2_end_time; - size_t gen2_elapsed_time = sample.gc_pause_time; - dynamic_heap_count_data.gen2_gc_percents[dynamic_heap_count_data.gen2_sample_index] = (float)gen2_elapsed_time * 100.0f / elapsed_between_gen2_gcs; + dprintf (6666, ("gen2 sample#%d: this GC end %I64d - last gen2 end %I64d = %I64d, GC elapsed %I64d, percent %.3f", + dynamic_heap_count_data.gen2_sample_index, end_gc_time, prev_gen2_end_time, elapsed_between_gen2_gcs, gen2_elapsed_time, g2_sample.gc_percent)); + dynamic_heap_count_data.gen2_sample_index = (dynamic_heap_count_data.gen2_sample_index + 1) % dynamic_heap_count_data_t::sample_size; + } - dprintf (6666, ("gen2 sample#%d: this GC end %I64d - last gen2 end %I64d = %I64d, GC elapsed %I64d, percent %.3f", - dynamic_heap_count_data.gen2_sample_index, end_gc_time, prev_gen2_end_time, elapsed_between_gen2_gcs, - gen2_elapsed_time, dynamic_heap_count_data.gen2_gc_percents[dynamic_heap_count_data.gen2_sample_index])); - dynamic_heap_count_data.gen2_sample_index = (dynamic_heap_count_data.gen2_sample_index + 1) % dynamic_heap_count_data_t::sample_size; + calculate_new_heap_count (); } - calculate_new_heap_count (); + last_suspended_end_time = end_gc_time; } #endif //DYNAMIC_HEAP_COUNT } @@ -22228,11 +22237,16 @@ void gc_heap::gc1() dprintf (6666, ("updating BGC %Id elapsed time to %I64d - %I64d = %I64d", dd_gc_clock (dd), end_gc_time, dd_time_clock (dd), dd_gc_elapsed_time (dd))); float bgc_percent = (float)dd_gc_elapsed_time (dd) * 100.0f / (float)time_since_last_gen2; - dynamic_heap_count_data.gen2_gc_percents[dynamic_heap_count_data.gen2_sample_index] = bgc_percent; + dynamic_heap_count_data_t::gen2_sample& g2_sample = dynamic_heap_count_data.gen2_samples[dynamic_heap_count_data.gen2_sample_index]; + g2_sample.gc_index = VolatileLoadWithoutBarrier (&(settings.gc_index)); + g2_sample.gc_percent = bgc_percent; dprintf (6666, ("gen2 sample %d elapsed %Id * 100 / time inbetween gen2 %Id = %.3f", dynamic_heap_count_data.gen2_sample_index, dd_gc_elapsed_time (dd), time_since_last_gen2, bgc_percent)); dynamic_heap_count_data.gen2_sample_index = (dynamic_heap_count_data.gen2_sample_index + 1) % dynamic_heap_count_data_t::sample_size; + (dynamic_heap_count_data.current_gen2_samples_count)++; gc_index_full_gc_end = dd_gc_clock (dynamic_data_of (0)); + + calculate_new_heap_count (); } #endif //DYNAMIC_HEAP_COUNT @@ -25075,7 +25089,6 @@ void gc_heap::recommission_heap() // copy some fields from heap0 - // this is copied to dd_previous_time_clock at the start of GC dd_time_clock (dd) = dd_time_clock (heap0_dd); @@ -25152,37 +25165,90 @@ float median_of_3 (float a, float b, float c) return b; } -size_t gc_heap::get_num_completed_gcs () +float log_with_base (float x, float base) { - size_t num_completed_gcs = settings.gc_index; -#ifdef BACKGROUND_GC - if (g_heaps[0]->is_bgc_in_progress ()) + assert (x > base); + + return (float)(log(x) / log(base)); +} + +float mean (float* arr, int size) +{ + float sum = 0.0; + + for (int i = 0; i < size; i++) { - num_completed_gcs--; - dprintf (6666, ("BGC in prog, completed GCs -> %Id", num_completed_gcs)); + sum += arr[i]; } -#endif //BACKGROUND_GC + return (sum / size); +} + +// Change it to a desired number if you want to print. +int max_times_to_print_tcp = 0; + +// Return the slope, and the average values in the avg arg. +float slope (float* y, int n, float* avg) +{ + assert (n > 0); - return num_completed_gcs; + if (n == 1) + { + dprintf (6666, ("only 1 tcp: %.3f, no slope", y[0])); + *avg = y[0]; + return 0.0; + } + + int sum_x = 0; + + for (int i = 0; i < n; i++) + { + sum_x += i; + + if (max_times_to_print_tcp >= 0) + { + dprintf (6666, ("%.3f, ", y[i])); + } + } + + float avg_x = (float)sum_x / n; + float avg_y = mean (y, n); + *avg = avg_y; + + float numerator = 0.0; + float denominator = 0.0; + + for (int i = 0; i < n; ++i) + { + numerator += ((float)i - avg_x) * (y[i] - avg_y); + denominator += ((float)i - avg_x) * (i - avg_x); + } + + max_times_to_print_tcp--; + + return (numerator / denominator); } int gc_heap::calculate_new_heap_count () { assert (dynamic_adaptation_mode == dynamic_adaptation_to_application_sizes); - size_t num_completed_gcs = get_num_completed_gcs (); - - dprintf (6666, ("current GC %Id(completed: %Id), prev completed GCs %Id, last full GC happened at index %Id", - VolatileLoadWithoutBarrier (&settings.gc_index), num_completed_gcs, dynamic_heap_count_data.prev_num_completed_gcs, gc_index_full_gc_end)); + dprintf (6666, ("current num of samples %Id (g2: %Id) prev processed %Id (g2: %Id), last full GC happened at index %Id", + dynamic_heap_count_data.current_samples_count, dynamic_heap_count_data.current_gen2_samples_count, + dynamic_heap_count_data.processed_samples_count, dynamic_heap_count_data.processed_gen2_samples_count, gc_index_full_gc_end)); - if (num_completed_gcs < (dynamic_heap_count_data.prev_num_completed_gcs + dynamic_heap_count_data_t::sample_size)) + if ((dynamic_heap_count_data.current_samples_count < (dynamic_heap_count_data.processed_samples_count + dynamic_heap_count_data_t::sample_size)) && + (dynamic_heap_count_data.current_gen2_samples_count < (dynamic_heap_count_data.processed_gen2_samples_count + dynamic_heap_count_data_t::sample_size))) { dprintf (6666, ("not enough GCs, skipping")); return n_heaps; } + bool process_eph_samples_p = (dynamic_heap_count_data.current_samples_count >= (dynamic_heap_count_data.processed_samples_count + dynamic_heap_count_data_t::sample_size)); + bool process_gen2_samples_p = (dynamic_heap_count_data.current_gen2_samples_count >= (dynamic_heap_count_data.processed_gen2_samples_count + dynamic_heap_count_data_t::sample_size)); + + size_t current_gc_index = VolatileLoadWithoutBarrier (&settings.gc_index); float median_gen2_tcp_percent = 0.0f; - if (gc_index_full_gc_end >= (settings.gc_index - dynamic_heap_count_data_t::sample_size)) + if (dynamic_heap_count_data.current_gen2_samples_count >= (dynamic_heap_count_data.processed_gen2_samples_count + dynamic_heap_count_data_t::sample_size)) { median_gen2_tcp_percent = dynamic_heap_count_data.get_median_gen2_gc_percent (); } @@ -25202,6 +25268,43 @@ int gc_heap::calculate_new_heap_count () } float median_throughput_cost_percent = median_of_3 (throughput_cost_percents[0], throughput_cost_percents[1], throughput_cost_percents[2]); + float avg_throughput_cost_percent = (float)((throughput_cost_percents[0] + throughput_cost_percents[1] + throughput_cost_percents[2]) / 3.0); + + // One of the reasons for outliers is something temporarily affected GC work. We pick the min tcp if the survival is very stable to avoid counting these outliers. + float min_tcp = throughput_cost_percents[0]; + size_t min_survived = dynamic_heap_count_data.samples[0].gc_survived_size; + uint64_t min_pause = dynamic_heap_count_data.samples[0].gc_pause_time; + for (int i = 1; i < dynamic_heap_count_data_t::sample_size; i++) + { + min_tcp = min (throughput_cost_percents[i], min_tcp); + min_survived = min (dynamic_heap_count_data.samples[i].gc_survived_size, min_survived); + min_pause = min (dynamic_heap_count_data.samples[i].gc_pause_time, min_pause); + } + + dprintf (6666, ("checking if samples are stable %Id %Id %Id, min tcp %.3f, min pause %I64d", + dynamic_heap_count_data.samples[0].gc_survived_size, dynamic_heap_count_data.samples[1].gc_survived_size, dynamic_heap_count_data.samples[2].gc_survived_size, + min_tcp, min_pause)); + + bool survived_stable_p = true; + if (min_survived > 0) + { + for (int i = 0; i < dynamic_heap_count_data_t::sample_size; i++) + { + dynamic_heap_count_data_t::sample& sample = dynamic_heap_count_data.samples[i]; + float diff = (float)(sample.gc_survived_size - min_survived) / (float)min_survived; + dprintf (6666, ("sample %d diff from min is %Id -> %.3f", i, (sample.gc_survived_size - min_survived), diff)); + if (diff >= 0.15) + { + survived_stable_p = false; + } + } + } + + if (survived_stable_p) + { + dprintf (6666, ("survived is stable, so we pick min tcp %.3f", min_tcp)); + median_throughput_cost_percent = min_tcp; + } // apply exponential smoothing and use 1/3 for the smoothing factor const float smoothing = 3; @@ -25216,10 +25319,13 @@ int gc_heap::calculate_new_heap_count () smoothed_median_throughput_cost_percent = median_throughput_cost_percent; } - dprintf (6666, ("median tcp: %.3f, smoothed tcp: %.3f, gen2 tcp %.3f(%.3f, %.3f, %.3f)", - median_throughput_cost_percent, smoothed_median_throughput_cost_percent, median_gen2_tcp_percent, - dynamic_heap_count_data.gen2_gc_percents[0], dynamic_heap_count_data.gen2_gc_percents[1], dynamic_heap_count_data.gen2_gc_percents[2])); + dprintf (6666, ("median tcp: %.3f, smoothed tcp: %.3f, avg tcp: %.3f, gen2 tcp %.3f(%.3f, %.3f, %.3f)", + median_throughput_cost_percent, smoothed_median_throughput_cost_percent, avg_throughput_cost_percent, median_gen2_tcp_percent, + dynamic_heap_count_data.gen2_samples[0].gc_percent, dynamic_heap_count_data.gen2_samples[1].gc_percent, dynamic_heap_count_data.gen2_samples[2].gc_percent)); + // + // I'm keeping the old logic for now just to handle gen2. + // size_t heap_size = 0; for (int i = 0; i < n_heaps; i++) { @@ -25247,7 +25353,9 @@ int gc_heap::calculate_new_heap_count () // we don't go all the way to the number of CPUs, but stay 1 or 2 short int step_up = (n_heaps + 1) / 2; int extra_heaps = 1 + (n_max_heaps >= 32); - step_up = min (step_up, n_max_heaps - extra_heaps - n_heaps); + int actual_n_max_heaps = n_max_heaps - extra_heaps; + int max_growth = max ((n_max_heaps / 4), 2); + step_up = min (step_up, (actual_n_max_heaps - n_heaps)); // on the way down, we essentially divide the heap count by 1.5 int step_down = (n_heaps + 1) / 3; @@ -25285,49 +25393,310 @@ int gc_heap::calculate_new_heap_count () dprintf (6666, ("stress %d -> %d", n_heaps, new_n_heaps)); #else //STRESS_DYNAMIC_HEAP_COUNT int new_n_heaps = n_heaps; - if (median_throughput_cost_percent > 10.0f) - { - // ramp up more agressively - use as many heaps as it would take to bring - // the tcp down to 5% - new_n_heaps = (int)(n_heaps * (median_throughput_cost_percent / 5.0)); - dprintf (6666, ("[CHP0] tcp %.3f -> %d * %.3f = %d", median_throughput_cost_percent, n_heaps, (median_throughput_cost_percent / 5.0), new_n_heaps)); - new_n_heaps = min (new_n_heaps, n_max_heaps - extra_heaps); - } - // if the median tcp is 10% or less, react slower - else if ((smoothed_median_throughput_cost_percent > 5.0f) || (median_gen2_tcp_percent > 10.0f)) + + // target_tcp should be configurable. + float target_tcp = 5.0; + float target_gen2_tcp = 10.0; + float log_base = (float)1.1; + + dynamic_heap_count_data.add_to_recorded_tcp (median_throughput_cost_percent); + + // This is the average of whatever is in the recorded tcp buffer. + float avg_recorded_tcp = 0.0; + + if (process_eph_samples_p) { - if (smoothed_median_throughput_cost_percent > 5.0f) + dynamic_heap_count_data.last_processed_stcp = smoothed_median_throughput_cost_percent; + + if ((median_throughput_cost_percent > 10.0f) || (smoothed_median_throughput_cost_percent > target_tcp)) { - dprintf (6666, ("[CHP1] stcp %.3f > 5, %d + %d = %d", smoothed_median_throughput_cost_percent, n_heaps, step_up, (n_heaps + step_up))); + // If median is high but stcp is lower than target, and if this situation continues, stcp will quickly be above target anyway; otherwise + // we treat it as an outlier. + if (smoothed_median_throughput_cost_percent > target_tcp) + { + float step_up_percent = log_with_base ((smoothed_median_throughput_cost_percent - target_tcp + log_base), log_base); + float step_up_float = (float)(step_up_percent / 100.0 * actual_n_max_heaps); + int step_up_int = (int)step_up_float; + + dprintf (6666, ("[CHP0] inc %d(%.3f), last inc %d, %Id GCs elapsed, last stcp %.3f", + step_up_int, step_up_float, (int)dynamic_heap_count_data.last_changed_count, + (current_gc_index - dynamic_heap_count_data.last_changed_gc_index), dynamic_heap_count_data.last_changed_stcp)); + + // Don't adjust if we just adjusted last time we checked, unless we are in an extreme situation. + if ((smoothed_median_throughput_cost_percent < 20.0f) && + (avg_throughput_cost_percent < 20.0f) && + ((current_gc_index - dynamic_heap_count_data.last_changed_gc_index) < (2 * dynamic_heap_count_data_t::sample_size))) + { + dprintf (6666, ("[CHP0] we just adjusted %Id GCs ago, skipping", (current_gc_index - dynamic_heap_count_data.last_changed_gc_index))); + } + else + { + if (step_up_int) + { + if (dynamic_heap_count_data.dec_failure_count) + { + dprintf (6666, ("[CHP0] intending to grow, reset dec failure count (was %d)", dynamic_heap_count_data.dec_failure_count)); + dynamic_heap_count_data.dec_failure_count = 0; + } + + if (((int)dynamic_heap_count_data.last_changed_count > 0) && (dynamic_heap_count_data.last_changed_gc_index > 0.0) && + ((current_gc_index - dynamic_heap_count_data.last_changed_gc_index) <= (3 * dynamic_heap_count_data_t::sample_size))) + { + dprintf (6666, ("[CHP0-0] just grew %d GCs ago, no change", (current_gc_index - dynamic_heap_count_data.last_changed_gc_index))); + step_up_int = 0; + } + else + { + // If the calculation tells us to grow, we should check to see if the slope has been coming down rapidly, if so there's no reason to grow. + int above_target_tcp_count = dynamic_heap_count_data.rearrange_recorded_tcp (); + float above_target_tcp_slope = slope (dynamic_heap_count_data.recorded_tcp_rearranged, above_target_tcp_count, &avg_recorded_tcp); + float diff_pct = (target_tcp - avg_recorded_tcp) / target_tcp; + float adjusted_target_tcp = dynamic_heap_count_data.get_range_upper (target_tcp); + + dprintf (6666, ("[CHP0] slope of last %d samples is %.3f. avg %.3f (%.3f%%), current tcp %.3f, adjusted target is %.3f, failure count is %d", + above_target_tcp_count, above_target_tcp_slope, avg_recorded_tcp, (diff_pct * 100.0), + median_throughput_cost_percent, adjusted_target_tcp, dynamic_heap_count_data.inc_failure_count)); + + if (dynamic_heap_count_data.is_tcp_in_range (diff_pct, above_target_tcp_slope)) + { + step_up_int = 0; + dprintf (6666, ("[CHP0-1] slope %.3f and already close to target %.3f (%.3f%%), no change", above_target_tcp_slope, avg_recorded_tcp, (diff_pct * 100.0))); + } + else + { + if (above_target_tcp_slope < 0.0) + { + // If we are already trending down and the tcp is small enough, just wait. + if ((median_throughput_cost_percent < adjusted_target_tcp) || (avg_recorded_tcp < adjusted_target_tcp)) + { + step_up_int = 0; + dprintf (6666, ("[CHP0-2] trending down, slope is %.3f, tcp is %.3f, avg is %.3f, already below adjusted target %.3f, no change", + above_target_tcp_slope, median_throughput_cost_percent, avg_recorded_tcp, adjusted_target_tcp)); + } + } + else + { + // We are trending up, but we have too few samples and the avg is already small enough. + if ((above_target_tcp_count <= dynamic_heap_count_data.inc_recheck_threshold) && (avg_recorded_tcp < adjusted_target_tcp)) + { + step_up_int = 0; + dprintf (6666, ("[CHP0-3] trending up, only %d samples, slope is %.3f, avg is %.3f already below adjusted target %.3f, no change", + above_target_tcp_count, above_target_tcp_slope, avg_recorded_tcp, adjusted_target_tcp)); + } + } + } + } + + // If we still decided to grow, check if we need to grow aggressively. + if (step_up_int) + { + if (((int)dynamic_heap_count_data.last_changed_count > 0) && (dynamic_heap_count_data.last_changed_gc_index > 0.0)) + { + (dynamic_heap_count_data.inc_failure_count)++; + dprintf (6666, ("[CHP0-4] just grew %d GCs ago, grow more aggressively from %d -> %d more heaps", + (current_gc_index - dynamic_heap_count_data.last_changed_gc_index), step_up_int, (step_up_int * (dynamic_heap_count_data.inc_failure_count + 1)))); + step_up_int *= dynamic_heap_count_data.inc_failure_count + 1; + } + } + } + + step_up_int = min (step_up_int, max_growth); + + new_n_heaps = n_heaps + step_up_int; + new_n_heaps = min (new_n_heaps, actual_n_max_heaps); + + // If we are going to grow to be very close to max heap, it's better to just grow to it. + if ((new_n_heaps < actual_n_max_heaps) && dynamic_heap_count_data.is_close_to_max (new_n_heaps, actual_n_max_heaps)) + { + dprintf (6666, ("[CHP0-5] %d is close to max heaps %d, grow to max", new_n_heaps, actual_n_max_heaps)); + new_n_heaps = actual_n_max_heaps; + } + + if (new_n_heaps > n_heaps) + { + dynamic_heap_count_data.last_changed_gc_index = current_gc_index; + dynamic_heap_count_data.last_changed_count = step_up_float; + dynamic_heap_count_data.last_changed_stcp = smoothed_median_throughput_cost_percent; + } + + dprintf (6666, ("[CHP0] tcp %.3f, stcp %.3f -> (%d * %.3f%% = %.3f) -> %d + %d = %d -> %d", + median_throughput_cost_percent, smoothed_median_throughput_cost_percent, + actual_n_max_heaps, step_up_percent, step_up_float, step_up_int, n_heaps, (n_heaps + step_up_int), new_n_heaps)); + } + } } else { - dprintf (6666, ("[CHP2] tcp %.3f > 10, %d + %d = %d", median_gen2_tcp_percent, n_heaps, step_up, (n_heaps + step_up))); + // When we are below target, we accumulate the distance to target and only adjust when we've accumulated enough in this state. Note that + // this can include tcp's that are slightly above target, as long as it's not high enough for us to adjust the heap count. If we are just + // oscillating around target, this makes those tcp's cancel each other out. + if (dynamic_heap_count_data.below_target_accumulation == 0) + { + dynamic_heap_count_data.first_below_target_gc_index = current_gc_index; + dynamic_heap_count_data.init_recorded_tcp (); + dynamic_heap_count_data.add_to_recorded_tcp (median_throughput_cost_percent); + } + dprintf (6666, ("[CHP1] last time adjusted %s by %d at GC#%Id (%Id GCs since), stcp was %.3f, now stcp is %.3f", + ((dynamic_heap_count_data.last_changed_count > 0.0) ? "up" : "down"), (int)dynamic_heap_count_data.last_changed_count, + dynamic_heap_count_data.last_changed_gc_index, (current_gc_index - dynamic_heap_count_data.last_changed_gc_index), + dynamic_heap_count_data.last_changed_stcp, smoothed_median_throughput_cost_percent)); + + float below_target_diff = target_tcp - median_throughput_cost_percent; + dynamic_heap_count_data.below_target_accumulation += below_target_diff; + + dprintf (6666, ("[CHP1] below target for the past %Id GCs, accumulated %.3f, min (10%% of max is %.2f, 20%% of hc is %.2f)", + (current_gc_index - dynamic_heap_count_data.first_below_target_gc_index), dynamic_heap_count_data.below_target_accumulation, + (actual_n_max_heaps * 0.1), (n_heaps * 0.2))); + + if (dynamic_heap_count_data.below_target_accumulation >= dynamic_heap_count_data.below_target_threshold) + { + int below_target_tcp_count = dynamic_heap_count_data.rearrange_recorded_tcp (); + float below_target_tcp_slope = slope (dynamic_heap_count_data.recorded_tcp, below_target_tcp_count, &avg_recorded_tcp); + float diff_pct = (target_tcp - smoothed_median_throughput_cost_percent) / target_tcp; + int step_down_int = (int)(diff_pct / 2.0 * n_heaps); + dprintf (6666, ("[CHP1] observed %d tcp's <= or ~ target, avg %.3f, slope %.3f, stcp %.3f below target, shrink by %.3f * %d = %d heaps", + below_target_tcp_count, avg_recorded_tcp, below_target_tcp_slope, (diff_pct * 100.0), (diff_pct * 50.0), n_heaps, step_down_int)); + + bool shrink_p = false; + if (dynamic_heap_count_data.is_tcp_in_range (diff_pct, below_target_tcp_slope)) + { + step_down_int = 0; + dprintf (6666, ("[CHP1-0] slope %.3f is flat and stcp is already close to target %.3f (%.3f%%), no change", + below_target_tcp_slope, smoothed_median_throughput_cost_percent, (diff_pct * 100.0))); + } + else + { + // If we adjusted last time and it was unsuccessful, we need to inc our failure count. + // If we have a non zero failure count, we don't want to adjust for a while if we continue to be in that same situation. + bool last_dec_p = (dynamic_heap_count_data.last_changed_gc_index > 0) && (dynamic_heap_count_data.last_changed_count < 0.0); + float last_dec_tcp_diff_pct = (last_dec_p ? + ((smoothed_median_throughput_cost_percent - dynamic_heap_count_data.last_changed_stcp) / dynamic_heap_count_data.last_changed_stcp) : 0.0f); + bool stable_p = last_dec_p && ((last_dec_tcp_diff_pct <= 0.2) && (last_dec_tcp_diff_pct >= -0.2)); + dprintf (6666, ("[CHP1] since last adjustment stcp changed %.3f->%.3f = %.3f%%, %s, dec_failure_count is %d", + dynamic_heap_count_data.last_changed_stcp, smoothed_median_throughput_cost_percent, (last_dec_tcp_diff_pct * 100.0), + (stable_p ? "stable" : "not stable"), dynamic_heap_count_data.dec_failure_count)); + + bool check_dec_p = true; + + if (stable_p) + { + if (dynamic_heap_count_data.dec_failure_count) + { + (dynamic_heap_count_data.dec_failure_count)++; + } + else + { + dynamic_heap_count_data.dec_failure_count = 1; + } + + if (dynamic_heap_count_data.dec_failure_count <= dynamic_heap_count_data.dec_failure_recheck_threshold) + { + check_dec_p = false; + dprintf (6666, ("[CHP1-1] dec was still unsuccessful, <= %d, no change", dynamic_heap_count_data.dec_failure_recheck_threshold)); + } + } + + if (check_dec_p) + { + dynamic_heap_count_data.dec_failure_count = 0; + + if (below_target_tcp_slope <= 0.0) + { + shrink_p = true; + } + else + { + // It's trending upwards, but if takes too many samples to get to target, we do want to shrink. + int num_samples_to_goal = (int)((target_tcp + below_target_tcp_slope - median_throughput_cost_percent) / below_target_tcp_slope); + bool far_below_goal_p = (num_samples_to_goal > (3 * dynamic_heap_count_data_t::sample_size)); + dprintf (6666, ("[CHP1] it'll take ((%.3f + %.3f - %.3f) / %.3f = %d) samples to get to target, %s", + target_tcp, below_target_tcp_slope, median_throughput_cost_percent, below_target_tcp_slope, + num_samples_to_goal, (far_below_goal_p ? "shrink" : "no change"))); + + if (far_below_goal_p) + { + // We could be in a situation where the slope changes directions but since we only compute one number, we take another look at + // the samples to make a better assessment by looking at the highest tcps and if their average is close to target, we don't shrink. + // + // TODO - we only check this when the slope is going up but since this includes the situation where the slope changes directions + // we should really be checking this regardless of the slope to handle that. + float highest_avg_tcp = 0.0; + int highest_count = dynamic_heap_count_data.highest_avg_recorded_tcp (below_target_tcp_count, avg_recorded_tcp, &highest_avg_tcp); + float highest_count_pct = (float)highest_count / (float)below_target_tcp_count; + + shrink_p = (highest_count_pct < 0.3) || (highest_avg_tcp < (target_tcp * 0.8)); + dprintf (6666, ("[CHP1-2] %d samples were above avg (%.3f%%), their avg is %.3f (%s)", + highest_count, (highest_count_pct * 100.0), highest_avg_tcp, (shrink_p ? "shrink" : "no change"))); + } + } + } + } + + if (shrink_p && step_down_int && (new_n_heaps > step_down_int)) + { + // TODO - if we see that it wants to shrink by 1 heap too many times, we do want to shrink. + if (step_down_int == 1) + { + step_down_int = 0; + dprintf (6666, ("[CHP1-3] don't shrink if it's just one heap. not worth it")); + } + + new_n_heaps -= step_down_int; + dprintf (6666, ("[CHP1] shrink by %d heaps -> %d", step_down_int, new_n_heaps)); + } + + // Always reinit the buffer as we want to look at the more recent history. + dynamic_heap_count_data.init_recorded_tcp (); + dynamic_heap_count_data.below_target_accumulation = 0; + } + + if (new_n_heaps < n_heaps) + { + dynamic_heap_count_data.last_changed_gc_index = current_gc_index; + dynamic_heap_count_data.last_changed_count = (float)(new_n_heaps - n_heaps); + dynamic_heap_count_data.last_changed_stcp = smoothed_median_throughput_cost_percent; + dprintf (6666, ("[CHP1] setting last changed gc index to %Id, count to %.3f, stcp to %.3f", + dynamic_heap_count_data.last_changed_gc_index, dynamic_heap_count_data.last_changed_count, dynamic_heap_count_data.last_changed_stcp)); + + if (dynamic_heap_count_data.inc_failure_count) + { + dprintf (6666, ("[CHP1] shrink, reset inc failure count (was %d)", dynamic_heap_count_data.inc_failure_count)); + dynamic_heap_count_data.inc_failure_count = 0; + } + } } - new_n_heaps += step_up; - } - // if we can save at least 1% more in time than we spend in space, increase number of heaps - else if ((tcp_reduction_per_step_up - scp_increase_per_step_up) >= 1.0f) - { - dprintf (6666, ("[CHP3] % .3f - % .3f = % .3f, % d + % d = % d", - tcp_reduction_per_step_up, scp_increase_per_step_up, (tcp_reduction_per_step_up - scp_increase_per_step_up), - n_heaps, step_up, (n_heaps + step_up))); - new_n_heaps += step_up; } - // if we can save at least 1% more in space than we spend in time, decrease number of heaps - else if ((smoothed_median_throughput_cost_percent < 1.0f) && - (median_gen2_tcp_percent < 5.0f) && - ((scp_decrease_per_step_down - tcp_increase_per_step_down) >= 1.0f)) + + if ((new_n_heaps == n_heaps) && !process_eph_samples_p && process_gen2_samples_p) { - dprintf (6666, ("[CHP4] stcp %.3f tcp %.3f, %.3f - %.3f = %.3f, %d + %d = %d", - smoothed_median_throughput_cost_percent, median_gen2_tcp_percent, - scp_decrease_per_step_down, tcp_increase_per_step_down, (scp_decrease_per_step_down - tcp_increase_per_step_down), - n_heaps, step_up, (n_heaps + step_up))); - new_n_heaps -= step_down; + // The gen2 samples only serve as a backstop so this is quite crude. + if (median_gen2_tcp_percent > target_gen2_tcp) + { + float step_up_percent = log_with_base ((median_gen2_tcp_percent - target_gen2_tcp + log_base), log_base); + float step_up_float = (float)(step_up_percent / 100.0 * actual_n_max_heaps); + new_n_heaps += (int)step_up_float; + new_n_heaps = min (new_n_heaps, actual_n_max_heaps); + dprintf (6666, ("[CHP2-0] gen2 tcp: %.3f, inc by %.3f%% = %d, %d -> %d", median_gen2_tcp_percent, step_up_percent, (int)step_up_float, n_heaps, new_n_heaps)); + + if ((new_n_heaps < actual_n_max_heaps) && dynamic_heap_count_data.is_close_to_max (new_n_heaps, actual_n_max_heaps)) + { + dprintf (6666, ("[CHP2-1] %d is close to max heaps %d, grow to max", new_n_heaps, actual_n_max_heaps)); + new_n_heaps = actual_n_max_heaps; + } + } + else if ((dynamic_heap_count_data.last_processed_stcp < 1.0) && + (median_gen2_tcp_percent < (target_gen2_tcp / 2)) && + (scp_decrease_per_step_down - tcp_increase_per_step_down >= 1.0f)) + { + new_n_heaps -= step_down; + dprintf (6666, ("[CHP3-0] last eph stcp: %.3f, gen2 tcp: %.3f, dec by %d, %d -> %d", + dynamic_heap_count_data.last_processed_stcp, median_gen2_tcp_percent, step_down, n_heaps, new_n_heaps)); + } } assert (new_n_heaps >= 1); - assert (new_n_heaps <= n_max_heaps); + assert (new_n_heaps <= actual_n_max_heaps); + #endif //STRESS_DYNAMIC_HEAP_COUNT // store data used for decision to emit in ETW event @@ -25350,13 +25719,28 @@ int gc_heap::calculate_new_heap_count () dynamic_heap_count_data.scp_decrease_per_step_down ); - dynamic_heap_count_data.prev_num_completed_gcs = num_completed_gcs; + if (process_eph_samples_p) + { + dprintf (6666, ("processed eph samples, updating processed %Id -> %Id", dynamic_heap_count_data.processed_samples_count, dynamic_heap_count_data.current_samples_count)); + dynamic_heap_count_data.processed_samples_count = dynamic_heap_count_data.current_samples_count; + } + + if (process_gen2_samples_p) + { + dprintf (6666, ("processed gen2 samples, updating processed %Id -> %Id", dynamic_heap_count_data.processed_gen2_samples_count, dynamic_heap_count_data.current_gen2_samples_count)); + dynamic_heap_count_data.processed_gen2_samples_count = dynamic_heap_count_data.current_gen2_samples_count; + } if (new_n_heaps != n_heaps) { - dprintf (6666, ("should change! %d->%d", n_heaps, new_n_heaps)); + dprintf (6666, ("GC#%Id should change! %d->%d (%s)", + VolatileLoadWithoutBarrier (&settings.gc_index), n_heaps, new_n_heaps, ((n_heaps < new_n_heaps) ? "INC" : "DEC"))); dynamic_heap_count_data.heap_count_to_change_to = new_n_heaps; dynamic_heap_count_data.should_change_heap_count = true; + dynamic_heap_count_data.init_recorded_tcp (); + dynamic_heap_count_data.below_target_accumulation = 0; + dynamic_heap_count_data.first_below_target_gc_index = current_gc_index; + dprintf (6666, ("CHANGING HC, resetting tcp index, below target")); } return new_n_heaps; @@ -25389,7 +25773,7 @@ void gc_heap::check_heap_count () if (dynamic_heap_count_data.new_n_heaps != n_heaps) { - dprintf (6666, ("prep to change from %d to %d", n_heaps, dynamic_heap_count_data.new_n_heaps)); + dprintf (6666, ("prep to change from %d to %d at GC#%Id", n_heaps, dynamic_heap_count_data.new_n_heaps, VolatileLoadWithoutBarrier (&settings.gc_index))); if (!prepare_to_change_heap_count (dynamic_heap_count_data.new_n_heaps)) { // we don't have sufficient resources - reset the new heap count @@ -25399,11 +25783,15 @@ void gc_heap::check_heap_count () if (dynamic_heap_count_data.new_n_heaps == n_heaps) { - // heap count stays the same, no work to do - dynamic_heap_count_data.prev_num_completed_gcs = get_num_completed_gcs (); + dynamic_heap_count_data.last_changed_gc_index = 0; + dynamic_heap_count_data.last_changed_count = 0.0; + + dynamic_heap_count_data.processed_samples_count = dynamic_heap_count_data.current_samples_count; + dynamic_heap_count_data.processed_gen2_samples_count = dynamic_heap_count_data.current_gen2_samples_count; dynamic_heap_count_data.should_change_heap_count = false; - dprintf (6666, ("heap count stays the same %d, no work to do, set prev completed to %Id", dynamic_heap_count_data.new_n_heaps, dynamic_heap_count_data.prev_num_completed_gcs)); + dprintf (6666, ("heap count stays the same %d, no work to do, set processed sample count to %Id", + dynamic_heap_count_data.new_n_heaps, dynamic_heap_count_data.current_samples_count)); return; } @@ -25443,17 +25831,14 @@ void gc_heap::check_heap_count () int old_n_heaps = n_heaps; - (dynamic_heap_count_data.heap_count_change_count)++; change_heap_count (dynamic_heap_count_data.new_n_heaps); GCToEEInterface::RestartEE(TRUE); dprintf (9999, ("h0 restarted EE")); - // we made changes to the heap count that will change the overhead, - // so change the smoothed overhead to reflect that - dynamic_heap_count_data.smoothed_median_throughput_cost_percent = dynamic_heap_count_data.smoothed_median_throughput_cost_percent / n_heaps * old_n_heaps; + dynamic_heap_count_data.smoothed_median_throughput_cost_percent = 0.0; - dprintf (6666, ("h0 finished changing, set should change to false!")); + dprintf (6666, ("h0 finished changing, set should change to false!\n")); dynamic_heap_count_data.should_change_heap_count = false; } @@ -25593,6 +25978,8 @@ bool gc_heap::prepare_to_change_heap_count (int new_n_heaps) bool gc_heap::change_heap_count (int new_n_heaps) { + uint64_t start_time = 0; + dprintf (9999, ("BEG heap%d changing %d->%d", heap_number, n_heaps, new_n_heaps)); // use this variable for clarity - n_heaps will change during the transition @@ -25617,11 +26004,9 @@ bool gc_heap::change_heap_count (int new_n_heaps) assert (dynamic_heap_count_data.new_n_heaps != old_n_heaps); - dprintf (9999, ("Waiting h0 heap%d changing %d->%d", heap_number, n_heaps, new_n_heaps)); - if (heap_number == 0) { - dprintf (3, ("switching heap count from %d to %d heaps", old_n_heaps, new_n_heaps)); + start_time = GetHighPrecisionTimeStamp (); // spread finalization data out to heaps coming into service // if this step fails, we can still continue @@ -25827,6 +26212,7 @@ bool gc_heap::change_heap_count (int new_n_heaps) gc_t_join.restart (); } } + #ifdef BACKGROUND_GC // there should be no items in the bgc_alloc_lock bgc_alloc_lock->check(); @@ -25837,23 +26223,31 @@ bool gc_heap::change_heap_count (int new_n_heaps) { // compute the total budget per generation over the old heaps // and figure out what the new budget per heap is - ptrdiff_t budget_per_heap[total_generation_count]; + ptrdiff_t new_alloc_per_heap[total_generation_count]; + size_t desired_alloc_per_heap[total_generation_count]; for (int gen_idx = 0; gen_idx < total_generation_count; gen_idx++) { - ptrdiff_t total_budget = 0; + ptrdiff_t total_new_alloc = 0; + size_t total_desired_alloc = 0; for (int i = 0; i < old_n_heaps; i++) { gc_heap* hp = g_heaps[i]; dynamic_data* dd = hp->dynamic_data_of (gen_idx); - total_budget += dd_new_allocation (dd); + total_new_alloc += dd_new_allocation (dd); + total_desired_alloc += dd_desired_allocation (dd); } // distribute the total budget for this generation over all new heaps if we are increasing heap count, // but keep the budget per heap if we are decreasing heap count int max_n_heaps = max (old_n_heaps, new_n_heaps); - budget_per_heap[gen_idx] = Align (total_budget/max_n_heaps, get_alignment_constant (gen_idx <= max_generation)); - - dprintf (6666, ("g%d: total budget: %zd budget per heap: %zd", gen_idx, total_budget, budget_per_heap[gen_idx])); + new_alloc_per_heap[gen_idx] = Align (total_new_alloc / max_n_heaps, get_alignment_constant (gen_idx <= max_generation)); + desired_alloc_per_heap[gen_idx] = Align (total_desired_alloc / max_n_heaps, get_alignment_constant (gen_idx <= max_generation)); + size_t allocated_in_budget = total_desired_alloc - total_new_alloc; + dprintf (6666, ("g%d: total budget %zd (%zd / heap), left in budget: %zd (%zd / heap), (allocated %Id, %.3f%%), min %zd", + gen_idx, total_desired_alloc, desired_alloc_per_heap[gen_idx], + total_new_alloc, new_alloc_per_heap[gen_idx], + allocated_in_budget, ((double)allocated_in_budget * 100.0 / (double)total_desired_alloc), + dd_min_size (g_heaps[0]->dynamic_data_of (gen_idx)))); } // distribute the new budget per heap over the new heaps @@ -25864,10 +26258,10 @@ bool gc_heap::change_heap_count (int new_n_heaps) for (int gen_idx = 0; gen_idx < total_generation_count; gen_idx++) { - // distribute the total budget over all heaps, but don't go below the min budget + // distribute the total leftover budget over all heaps. dynamic_data* dd = hp->dynamic_data_of (gen_idx); - dd_new_allocation (dd) = max (budget_per_heap[gen_idx], (ptrdiff_t)dd_min_size (dd)); - dd_desired_allocation (dd) = dd_new_allocation (dd); + dd_new_allocation (dd) = new_alloc_per_heap[gen_idx]; + dd_desired_allocation (dd) = max (desired_alloc_per_heap[gen_idx], dd_min_size (dd)); // recompute dd_fragmentation and dd_current_size generation* gen = hp->generation_of (gen_idx); @@ -25876,10 +26270,11 @@ bool gc_heap::change_heap_count (int new_n_heaps) assert (gen_size >= dd_fragmentation (dd)); dd_current_size (dd) = gen_size - dd_fragmentation (dd); - dprintf (6666, ("h%d g%d: new allocation: %zd generation_size: %zd fragmentation: %zd current_size: %zd", + dprintf (3, ("h%d g%d: budget: %zd, left in budget: %zd, %zd generation_size: %zd fragmentation: %zd current_size: %zd", i, gen_idx, - dd_new_allocation (dd), + desired_alloc_per_heap[gen_idx], + new_alloc_per_heap[gen_idx], gen_size, dd_fragmentation (dd), dd_current_size (dd))); @@ -25916,6 +26311,11 @@ bool gc_heap::change_heap_count (int new_n_heaps) } } + if (heap_number == 0) + { + change_heap_count_time = GetHighPrecisionTimeStamp() - start_time; + } + return true; } @@ -48405,6 +48805,10 @@ HRESULT GCHeap::Initialize() // This needs to be different from our initial heap count so we can make sure we wait for // the idle threads correctly in gc_thread_function. gc_heap::dynamic_heap_count_data.last_n_heaps = 0; + // This should be adjusted based on the target tcp. See comments in gcpriv.h + gc_heap::dynamic_heap_count_data.below_target_threshold = 10.0; + gc_heap::dynamic_heap_count_data.inc_recheck_threshold = 5; + gc_heap::dynamic_heap_count_data.dec_failure_recheck_threshold = 5; } #endif //DYNAMIC_HEAP_COUNT GCScan::GcRuntimeStructuresValid (TRUE); diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 71dc19b9f0c676..788cbff9f5e507 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -2556,8 +2556,6 @@ class gc_heap // re-initialize a heap in preparation to putting it back into service PER_HEAP_METHOD void recommission_heap(); - PER_HEAP_ISOLATED_METHOD size_t get_num_completed_gcs(); - PER_HEAP_ISOLATED_METHOD int calculate_new_heap_count(); // check if we should change the heap count @@ -4238,21 +4236,166 @@ class gc_heap struct dynamic_heap_count_data_t { static const int sample_size = 3; + static const int recorded_tcp_array_size = 64; struct sample { uint64_t elapsed_between_gcs; // time between gcs in microseconds (this should really be between_pauses) uint64_t gc_pause_time; // pause time for this GC uint64_t msl_wait_time; + size_t gc_survived_size; }; uint32_t sample_index; sample samples[sample_size]; - size_t prev_num_completed_gcs; + + size_t current_samples_count; + size_t processed_samples_count; + + // + // We need to observe the history of tcp's so record them in a small buffer. + // + float recorded_tcp_rearranged[recorded_tcp_array_size]; + float recorded_tcp[recorded_tcp_array_size]; + int recorded_tcp_index; + int total_recorded_tcp; + + int add_to_recorded_tcp (float tcp) + { + total_recorded_tcp++; + + recorded_tcp[recorded_tcp_index] = tcp; + recorded_tcp_index++; + if (recorded_tcp_index == recorded_tcp_array_size) + { + recorded_tcp_index = 0; + } + + return recorded_tcp_index; + } + + int rearrange_recorded_tcp () + { + int count = recorded_tcp_array_size; + int copied_count = 0; + + if (total_recorded_tcp >= recorded_tcp_array_size) + { + int earlier_entry_size = recorded_tcp_array_size - recorded_tcp_index; + memcpy (recorded_tcp_rearranged, (recorded_tcp + recorded_tcp_index), (earlier_entry_size * sizeof (float))); + + copied_count = earlier_entry_size; + } + + if (recorded_tcp_index) + { + memcpy ((recorded_tcp_rearranged + copied_count), recorded_tcp, (recorded_tcp_index * sizeof (float))); + copied_count += recorded_tcp_index; + } + + return copied_count; + } + + int highest_avg_recorded_tcp (int count, float avg, float* highest_avg) + { + float highest_sum = 0.0; + int highest_count = 0; + + for (int i = 0; i < count; i++) + { + if (recorded_tcp_rearranged[i] > avg) + { + highest_count++; + highest_sum += recorded_tcp_rearranged[i]; + } + } + + if (highest_count) + { + *highest_avg = highest_sum / highest_count; + } + + return highest_count; + } + + void init_recorded_tcp () + { + total_recorded_tcp = 0; + recorded_tcp_index = 0; + dprintf (6666, ("INIT tcp buffer")); + } + + int get_recorded_tcp_count () { return total_recorded_tcp; } + + // + // Maintain some info about last time we did change heap count. + // + size_t last_changed_gc_index; + // This is intentionally kept as a float for precision. + float last_changed_count; + float last_changed_stcp; + + // + // For tuning above/below target tcp. + // + // If we just increased the heap count and immediately need to grow again, that counts as a failure. + // The higher the failure count, the more aggressive we should grow. + int inc_failure_count; + + // If we are trending up and the tcp is already close enough to target, we need this many samples + // before we adjust. + int inc_recheck_threshold; + + // If we shrink and the stcp doesn't change much, that counts as a failure. For the below target case + // it's fine to stay here for a while. Either it'll naturally change and break out of this situation + // or we wait for a while before we re-evaluate. How long we wait is defined by dec_recheck_threshold + // each time our calculation tells us to shrink. + int dec_failure_count; + int dec_failure_recheck_threshold; + + // If we continue to be below target for an extended period of time, ie, we've accumulated more than + // below_target_threshold, we want to reduce the heap count. + float below_target_accumulation; + float below_target_threshold; + + // Currently only used for dprintf. + size_t first_below_target_gc_index; + + float get_range_upper (float t) + { + return (t * 1.2f); + } + + bool is_tcp_in_range (float diff_pct, float slope) + { + return ((diff_pct <= 0.2) && (diff_pct >= -0.2) && (slope <= 0.1) && (slope >= -0.1)); + } + + bool is_close_to_max (int new_n, int max) + { + return ((max - new_n) <= (max / 10)); + } + + // + // gen2 GCs are handled separately only as a backstop. + // + struct gen2_sample + { + // Recording the gen2 GC indices so we know how far apart they are. Currently unused + // but we should consider how much value there is if they are very far apart. + size_t gc_index; + // This is (gc_elapsed_time / time inbetween this and the last gen2 GC) + float gc_percent; + }; uint32_t gen2_sample_index; - // This is (gc_elapsed_time / time inbetween this and the last gen2 GC) - float gen2_gc_percents[sample_size]; + gen2_sample gen2_samples[sample_size]; + + size_t current_gen2_samples_count; + size_t processed_gen2_samples_count; + + // This records the stcp last time we processed ephemeral samples. We use it + float last_processed_stcp; float median_throughput_cost_percent; // estimated overhead of allocator + gc float smoothed_median_throughput_cost_percent; // exponentially smoothed version @@ -4271,14 +4414,13 @@ class gc_heap bool should_change_heap_count; int heap_count_to_change_to; - int heap_count_change_count; #ifdef STRESS_DYNAMIC_HEAP_COUNT int lowest_heap_with_msl_uoh; #endif //STRESS_DYNAMIC_HEAP_COUNT float get_median_gen2_gc_percent() { - return median_of_3 (gen2_gc_percents[0], gen2_gc_percents[1], gen2_gc_percents[2]); + return median_of_3 (gen2_samples[0].gc_percent, gen2_samples[1].gc_percent, gen2_samples[2].gc_percent); } }; PER_HEAP_ISOLATED_FIELD_MAINTAINED dynamic_heap_count_data_t dynamic_heap_count_data; @@ -4475,6 +4617,9 @@ class gc_heap // at the beginning of a BGC and the PM triggered full GCs // fall into this case. PER_HEAP_ISOLATED_FIELD_DIAG_ONLY uint64_t suspended_start_time; + // Right now this is diag only but may be used functionally later. + PER_HEAP_ISOLATED_FIELD_DIAG_ONLY uint64_t change_heap_count_time; + // TEMP END PER_HEAP_ISOLATED_FIELD_DIAG_ONLY uint64_t end_gc_time; PER_HEAP_ISOLATED_FIELD_DIAG_ONLY uint64_t total_suspended_time; PER_HEAP_ISOLATED_FIELD_DIAG_ONLY uint64_t process_start_time;