Skip to content

Commit d30a312

Browse files
author
duke
committed
Automatic merge of jdk:master into master
2 parents 845964c + aac5c2a commit d30a312

13 files changed

+339
-34
lines changed

src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp

Lines changed: 219 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,36 @@
2727
#include "gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp"
2828
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
2929
#include "gc/shenandoah/shenandoahFreeSet.hpp"
30-
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
31-
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
3230
#include "logging/log.hpp"
3331
#include "logging/logTag.hpp"
3432
#include "utilities/quickSort.hpp"
3533

34+
// These constants are used to adjust the margin of error for the moving
35+
// average of the allocation rate and cycle time. The units are standard
36+
// deviations.
37+
const double ShenandoahAdaptiveHeuristics::FULL_PENALTY_SD = 0.2;
38+
const double ShenandoahAdaptiveHeuristics::DEGENERATE_PENALTY_SD = 0.1;
39+
40+
// These are used to decide if we want to make any adjustments at all
41+
// at the end of a successful concurrent cycle.
42+
const double ShenandoahAdaptiveHeuristics::LOWEST_EXPECTED_AVAILABLE_AT_END = -0.5;
43+
const double ShenandoahAdaptiveHeuristics::HIGHEST_EXPECTED_AVAILABLE_AT_END = 0.5;
44+
45+
// These values are the confidence interval expressed as standard deviations.
46+
// At the minimum confidence level, there is a 25% chance that the true value of
47+
// the estimate (average cycle time or allocation rate) is not more than
48+
// MINIMUM_CONFIDENCE standard deviations away from our estimate. Similarly, the
49+
// MAXIMUM_CONFIDENCE interval here means there is a one in a thousand chance
50+
// that the true value of our estimate is outside the interval. These are used
51+
// as bounds on the adjustments applied at the outcome of a GC cycle.
52+
const double ShenandoahAdaptiveHeuristics::MINIMUM_CONFIDENCE = 0.319; // 25%
53+
const double ShenandoahAdaptiveHeuristics::MAXIMUM_CONFIDENCE = 3.291; // 99.9%
54+
3655
ShenandoahAdaptiveHeuristics::ShenandoahAdaptiveHeuristics() :
37-
ShenandoahHeuristics() {}
56+
ShenandoahHeuristics(),
57+
_margin_of_error_sd(ShenandoahAdaptiveInitialConfidence),
58+
_spike_threshold_sd(ShenandoahAdaptiveInitialSpikeThreshold),
59+
_last_trigger(OTHER) { }
3860

3961
ShenandoahAdaptiveHeuristics::~ShenandoahAdaptiveHeuristics() {}
4062

@@ -98,20 +120,94 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand
98120

99121
void ShenandoahAdaptiveHeuristics::record_cycle_start() {
100122
ShenandoahHeuristics::record_cycle_start();
123+
_allocation_rate.allocation_counter_reset();
124+
}
125+
126+
void ShenandoahAdaptiveHeuristics::record_success_concurrent() {
127+
ShenandoahHeuristics::record_success_concurrent();
128+
129+
size_t available = ShenandoahHeap::heap()->free_set()->available();
130+
131+
_available.add(available);
132+
double z_score = 0.0;
133+
if (_available.sd() > 0) {
134+
z_score = (available - _available.avg()) / _available.sd();
135+
}
136+
137+
log_debug(gc, ergo)("Available: " SIZE_FORMAT " %sB, z-score=%.3f. Average available: %.1f %sB +/- %.1f %sB.",
138+
byte_size_in_proper_unit(available), proper_unit_for_byte_size(available),
139+
z_score,
140+
byte_size_in_proper_unit(_available.avg()), proper_unit_for_byte_size(_available.avg()),
141+
byte_size_in_proper_unit(_available.sd()), proper_unit_for_byte_size(_available.sd()));
142+
143+
// In the case when a concurrent GC cycle completes successfully but with an
144+
// unusually small amount of available memory we will adjust our trigger
145+
// parameters so that they are more likely to initiate a new cycle.
146+
// Conversely, when a GC cycle results in an above average amount of available
147+
// memory, we will adjust the trigger parameters to be less likely to initiate
148+
// a GC cycle.
149+
//
150+
// The z-score we've computed is in no way statistically related to the
151+
// trigger parameters, but it has the nice property that worse z-scores for
152+
// available memory indicate making larger adjustments to the trigger
153+
// parameters. It also results in fewer adjustments as the application
154+
// stabilizes.
155+
//
156+
// In order to avoid making endless and likely unnecessary adjustments to the
157+
// trigger parameters, the change in available memory (with respect to the
158+
// average) at the end of a cycle must be beyond these threshold values.
159+
if (z_score < LOWEST_EXPECTED_AVAILABLE_AT_END ||
160+
z_score > HIGHEST_EXPECTED_AVAILABLE_AT_END) {
161+
// The sign is flipped because a negative z-score indicates that the
162+
// available memory at the end of the cycle is below average. Positive
163+
// adjustments make the triggers more sensitive (i.e., more likely to fire).
164+
// The z-score also gives us a measure of just how far below normal. This
165+
// property allows us to adjust the trigger parameters proportionally.
166+
//
167+
// The `100` here is used to attenuate the size of our adjustments. This
168+
// number was chosen empirically. It also means the adjustments at the end of
169+
// a concurrent cycle are an order of magnitude smaller than the adjustments
170+
// made for a degenerated or full GC cycle (which themselves were also
171+
// chosen empirically).
172+
adjust_last_trigger_parameters(z_score / -100);
173+
}
174+
}
175+
176+
void ShenandoahAdaptiveHeuristics::record_success_degenerated() {
177+
ShenandoahHeuristics::record_success_degenerated();
178+
// Adjust both trigger's parameters in the case of a degenerated GC because
179+
// either of them should have triggered earlier to avoid this case.
180+
adjust_margin_of_error(DEGENERATE_PENALTY_SD);
181+
adjust_spike_threshold(DEGENERATE_PENALTY_SD);
101182
}
102183

103-
bool ShenandoahAdaptiveHeuristics::should_start_gc() const {
184+
void ShenandoahAdaptiveHeuristics::record_success_full() {
185+
ShenandoahHeuristics::record_success_full();
186+
// Adjust both trigger's parameters in the case of a full GC because
187+
// either of them should have triggered earlier to avoid this case.
188+
adjust_margin_of_error(FULL_PENALTY_SD);
189+
adjust_spike_threshold(FULL_PENALTY_SD);
190+
}
191+
192+
static double saturate(double value, double min, double max) {
193+
return MAX2(MIN2(value, max), min);
194+
}
195+
196+
bool ShenandoahAdaptiveHeuristics::should_start_gc() {
104197
ShenandoahHeap* heap = ShenandoahHeap::heap();
105198
size_t max_capacity = heap->max_capacity();
106199
size_t capacity = heap->soft_max_capacity();
107200
size_t available = heap->free_set()->available();
201+
size_t allocated = heap->bytes_allocated_since_gc_start();
108202

109203
// Make sure the code below treats available without the soft tail.
110204
size_t soft_tail = max_capacity - capacity;
111205
available = (available > soft_tail) ? (available - soft_tail) : 0;
112206

113-
// Check if we are falling below the worst limit, time to trigger the GC, regardless of
114-
// anything else.
207+
// Track allocation rate even if we decide to start a cycle for other reasons.
208+
double rate = _allocation_rate.sample(allocated);
209+
_last_trigger = OTHER;
210+
115211
size_t min_threshold = capacity / 100 * ShenandoahMinFreeThreshold;
116212
if (available < min_threshold) {
117213
log_info(gc)("Trigger: Free (" SIZE_FORMAT "%s) is below minimum threshold (" SIZE_FORMAT "%s)",
@@ -120,7 +216,6 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() const {
120216
return true;
121217
}
122218

123-
// Check if are need to learn a bit about the application
124219
const size_t max_learn = ShenandoahLearningSteps;
125220
if (_gc_times_learned < max_learn) {
126221
size_t init_threshold = capacity / 100 * ShenandoahInitFreeThreshold;
@@ -136,7 +231,6 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() const {
136231
// Check if allocation headroom is still okay. This also factors in:
137232
// 1. Some space to absorb allocation spikes
138233
// 2. Accumulated penalties from Degenerated and Full GC
139-
140234
size_t allocation_headroom = available;
141235

142236
size_t spike_headroom = capacity / 100 * ShenandoahAllocSpikeFactor;
@@ -145,24 +239,127 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() const {
145239
allocation_headroom -= MIN2(allocation_headroom, spike_headroom);
146240
allocation_headroom -= MIN2(allocation_headroom, penalties);
147241

148-
// TODO: Allocation rate is way too averaged to be useful during state changes
149-
150-
double average_gc = _gc_time_history->avg();
151-
double time_since_last = time_since_last_gc();
152-
double allocation_rate = heap->bytes_allocated_since_gc_start() / time_since_last;
242+
double avg_cycle_time = _gc_time_history->davg() + (_margin_of_error_sd * _gc_time_history->dsd());
243+
double avg_alloc_rate = _allocation_rate.upper_bound(_margin_of_error_sd);
244+
if (avg_cycle_time > allocation_headroom / avg_alloc_rate) {
245+
log_info(gc)("Trigger: Average GC time (%.2f ms) is above the time for average allocation rate (%.0f %sB/s) to deplete free headroom (" SIZE_FORMAT "%s) (margin of error = %.2f)",
246+
avg_cycle_time * 1000,
247+
byte_size_in_proper_unit(avg_alloc_rate), proper_unit_for_byte_size(avg_alloc_rate),
248+
byte_size_in_proper_unit(allocation_headroom), proper_unit_for_byte_size(allocation_headroom),
249+
_margin_of_error_sd);
153250

154-
if (average_gc > allocation_headroom / allocation_rate) {
155-
log_info(gc)("Trigger: Average GC time (%.2f ms) is above the time for allocation rate (%.0f %sB/s) to deplete free headroom (" SIZE_FORMAT "%s)",
156-
average_gc * 1000,
157-
byte_size_in_proper_unit(allocation_rate), proper_unit_for_byte_size(allocation_rate),
158-
byte_size_in_proper_unit(allocation_headroom), proper_unit_for_byte_size(allocation_headroom));
159251
log_info(gc, ergo)("Free headroom: " SIZE_FORMAT "%s (free) - " SIZE_FORMAT "%s (spike) - " SIZE_FORMAT "%s (penalties) = " SIZE_FORMAT "%s",
160-
byte_size_in_proper_unit(available), proper_unit_for_byte_size(available),
161-
byte_size_in_proper_unit(spike_headroom), proper_unit_for_byte_size(spike_headroom),
162-
byte_size_in_proper_unit(penalties), proper_unit_for_byte_size(penalties),
163-
byte_size_in_proper_unit(allocation_headroom), proper_unit_for_byte_size(allocation_headroom));
252+
byte_size_in_proper_unit(available), proper_unit_for_byte_size(available),
253+
byte_size_in_proper_unit(spike_headroom), proper_unit_for_byte_size(spike_headroom),
254+
byte_size_in_proper_unit(penalties), proper_unit_for_byte_size(penalties),
255+
byte_size_in_proper_unit(allocation_headroom), proper_unit_for_byte_size(allocation_headroom));
256+
257+
_last_trigger = RATE;
258+
return true;
259+
}
260+
261+
bool is_spiking = _allocation_rate.is_spiking(rate, _spike_threshold_sd);
262+
if (is_spiking && avg_cycle_time > allocation_headroom / rate) {
263+
log_info(gc)("Trigger: Average GC time (%.2f ms) is above the time for instantaneous allocation rate (%.0f %sB/s) to deplete free headroom (" SIZE_FORMAT "%s) (spike threshold = %.2f)",
264+
avg_cycle_time * 1000,
265+
byte_size_in_proper_unit(rate), proper_unit_for_byte_size(rate),
266+
byte_size_in_proper_unit(allocation_headroom), proper_unit_for_byte_size(allocation_headroom),
267+
_spike_threshold_sd);
268+
_last_trigger = SPIKE;
164269
return true;
165270
}
166271

167272
return ShenandoahHeuristics::should_start_gc();
168273
}
274+
275+
void ShenandoahAdaptiveHeuristics::adjust_last_trigger_parameters(double amount) {
276+
switch (_last_trigger) {
277+
case RATE:
278+
adjust_margin_of_error(amount);
279+
break;
280+
case SPIKE:
281+
adjust_spike_threshold(amount);
282+
break;
283+
case OTHER:
284+
// nothing to adjust here.
285+
break;
286+
default:
287+
ShouldNotReachHere();
288+
}
289+
}
290+
291+
void ShenandoahAdaptiveHeuristics::adjust_margin_of_error(double amount) {
292+
_margin_of_error_sd = saturate(_margin_of_error_sd + amount, MINIMUM_CONFIDENCE, MAXIMUM_CONFIDENCE);
293+
log_debug(gc, ergo)("Margin of error now %.2f", _margin_of_error_sd);
294+
}
295+
296+
void ShenandoahAdaptiveHeuristics::adjust_spike_threshold(double amount) {
297+
_spike_threshold_sd = saturate(_spike_threshold_sd - amount, MINIMUM_CONFIDENCE, MAXIMUM_CONFIDENCE);
298+
log_debug(gc, ergo)("Spike threshold now: %.2f", _spike_threshold_sd);
299+
}
300+
301+
ShenandoahAllocationRate::ShenandoahAllocationRate() :
302+
_last_sample_time(os::elapsedTime()),
303+
_last_sample_value(0),
304+
_interval_sec(1.0 / ShenandoahAdaptiveSampleFrequencyHz),
305+
_rate(int(ShenandoahAdaptiveSampleSizeSeconds * ShenandoahAdaptiveSampleFrequencyHz), ShenandoahAdaptiveDecayFactor),
306+
_rate_avg(int(ShenandoahAdaptiveSampleSizeSeconds * ShenandoahAdaptiveSampleFrequencyHz), ShenandoahAdaptiveDecayFactor) {
307+
}
308+
309+
double ShenandoahAllocationRate::sample(size_t allocated) {
310+
double now = os::elapsedTime();
311+
double rate = 0.0;
312+
if (now - _last_sample_time > _interval_sec) {
313+
if (allocated >= _last_sample_value) {
314+
rate = instantaneous_rate(now, allocated);
315+
_rate.add(rate);
316+
_rate_avg.add(_rate.avg());
317+
}
318+
319+
_last_sample_time = now;
320+
_last_sample_value = allocated;
321+
}
322+
return rate;
323+
}
324+
325+
double ShenandoahAllocationRate::upper_bound(double sds) const {
326+
// Here we are using the standard deviation of the computed running
327+
// average, rather than the standard deviation of the samples that went
328+
// into the moving average. This is a much more stable value and is tied
329+
// to the actual statistic in use (moving average over samples of averages).
330+
return _rate.davg() + (sds * _rate_avg.dsd());
331+
}
332+
333+
void ShenandoahAllocationRate::allocation_counter_reset() {
334+
_last_sample_time = os::elapsedTime();
335+
_last_sample_value = 0;
336+
}
337+
338+
bool ShenandoahAllocationRate::is_spiking(double rate, double threshold) const {
339+
if (rate <= 0.0) {
340+
return false;
341+
}
342+
343+
double sd = _rate.sd();
344+
if (sd > 0) {
345+
// There is a small chance that that rate has already been sampled, but it
346+
// seems not to matter in practice.
347+
double z_score = (rate - _rate.avg()) / sd;
348+
if (z_score > threshold) {
349+
return true;
350+
}
351+
}
352+
return false;
353+
}
354+
355+
double ShenandoahAllocationRate::instantaneous_rate(size_t allocated) const {
356+
return instantaneous_rate(os::elapsedTime(), allocated);
357+
}
358+
359+
double ShenandoahAllocationRate::instantaneous_rate(double time, size_t allocated) const {
360+
size_t last_value = _last_sample_value;
361+
double last_time = _last_sample_time;
362+
size_t allocation_delta = (allocated > last_value) ? (allocated - last_value) : 0;
363+
double time_delta_sec = time - last_time;
364+
return (time_delta_sec > 0) ? (allocation_delta / time_delta_sec) : 0;
365+
}

src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@
2929
#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
3030
#include "utilities/numberSeq.hpp"
3131

32+
class ShenandoahAllocationRate : public CHeapObj<mtGC> {
33+
public:
34+
explicit ShenandoahAllocationRate();
35+
void allocation_counter_reset();
36+
37+
double sample(size_t allocated);
38+
39+
double instantaneous_rate(size_t allocated) const;
40+
double upper_bound(double sds) const;
41+
bool is_spiking(double rate, double threshold) const;
42+
43+
private:
44+
45+
double instantaneous_rate(double time, size_t allocated) const;
46+
47+
double _last_sample_time;
48+
size_t _last_sample_value;
49+
double _interval_sec;
50+
TruncatedSeq _rate;
51+
TruncatedSeq _rate_avg;
52+
};
53+
3254
class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
3355
public:
3456
ShenandoahAdaptiveHeuristics();
@@ -40,12 +62,70 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
4062
size_t actual_free);
4163

4264
void record_cycle_start();
65+
void record_success_concurrent();
66+
void record_success_degenerated();
67+
void record_success_full();
4368

44-
virtual bool should_start_gc() const;
69+
virtual bool should_start_gc();
4570

4671
virtual const char* name() { return "Adaptive"; }
4772
virtual bool is_diagnostic() { return false; }
4873
virtual bool is_experimental() { return false; }
74+
75+
private:
76+
// These are used to adjust the margin of error and the spike threshold
77+
// in response to GC cycle outcomes. These values are shared, but the
78+
// margin of error and spike threshold trend in opposite directions.
79+
const static double FULL_PENALTY_SD;
80+
const static double DEGENERATE_PENALTY_SD;
81+
82+
const static double MINIMUM_CONFIDENCE;
83+
const static double MAXIMUM_CONFIDENCE;
84+
85+
const static double LOWEST_EXPECTED_AVAILABLE_AT_END;
86+
const static double HIGHEST_EXPECTED_AVAILABLE_AT_END;
87+
88+
friend class ShenandoahAllocationRate;
89+
90+
// Used to record the last trigger that signaled to start a GC.
91+
// This itself is used to decide whether or not to adjust the margin of
92+
// error for the average cycle time and allocation rate or the allocation
93+
// spike detection threshold.
94+
enum Trigger {
95+
SPIKE, RATE, OTHER
96+
};
97+
98+
void adjust_last_trigger_parameters(double amount);
99+
void adjust_margin_of_error(double amount);
100+
void adjust_spike_threshold(double amount);
101+
102+
ShenandoahAllocationRate _allocation_rate;
103+
104+
// The margin of error expressed in standard deviations to add to our
105+
// average cycle time and allocation rate. As this value increases we
106+
// tend to over estimate the rate at which mutators will deplete the
107+
// heap. In other words, erring on the side of caution will trigger more
108+
// concurrent GCs.
109+
double _margin_of_error_sd;
110+
111+
// The allocation spike threshold is expressed in standard deviations.
112+
// If the standard deviation of the most recent sample of the allocation
113+
// rate exceeds this threshold, a GC cycle is started. As this value
114+
// decreases the sensitivity to allocation spikes increases. In other
115+
// words, lowering the spike threshold will tend to increase the number
116+
// of concurrent GCs.
117+
double _spike_threshold_sd;
118+
119+
// Remember which trigger is responsible for the last GC cycle. When the
120+
// outcome of the cycle is evaluated we will adjust the parameters for the
121+
// corresponding triggers. Note that successful outcomes will raise
122+
// the spike threshold and lower the margin of error.
123+
Trigger _last_trigger;
124+
125+
// Keep track of the available memory at the end of a GC cycle. This
126+
// establishes what is 'normal' for the application and is used as a
127+
// source of feedback to adjust trigger parameters.
128+
TruncatedSeq _available;
49129
};
50130

51131
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHADAPTIVEHEURISTICS_HPP

0 commit comments

Comments
 (0)