27
27
#include " gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp"
28
28
#include " gc/shenandoah/shenandoahCollectionSet.hpp"
29
29
#include " gc/shenandoah/shenandoahFreeSet.hpp"
30
- #include " gc/shenandoah/shenandoahHeap.inline.hpp"
31
- #include " gc/shenandoah/shenandoahHeapRegion.inline.hpp"
32
30
#include " logging/log.hpp"
33
31
#include " logging/logTag.hpp"
34
32
#include " utilities/quickSort.hpp"
35
33
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
+
36
55
ShenandoahAdaptiveHeuristics::ShenandoahAdaptiveHeuristics () :
37
- ShenandoahHeuristics() {}
56
+ ShenandoahHeuristics(),
57
+ _margin_of_error_sd(ShenandoahAdaptiveInitialConfidence),
58
+ _spike_threshold_sd(ShenandoahAdaptiveInitialSpikeThreshold),
59
+ _last_trigger(OTHER) { }
38
60
39
61
ShenandoahAdaptiveHeuristics::~ShenandoahAdaptiveHeuristics () {}
40
62
@@ -98,20 +120,94 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand
98
120
99
121
void ShenandoahAdaptiveHeuristics::record_cycle_start () {
100
122
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);
101
182
}
102
183
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 () {
104
197
ShenandoahHeap* heap = ShenandoahHeap::heap ();
105
198
size_t max_capacity = heap->max_capacity ();
106
199
size_t capacity = heap->soft_max_capacity ();
107
200
size_t available = heap->free_set ()->available ();
201
+ size_t allocated = heap->bytes_allocated_since_gc_start ();
108
202
109
203
// Make sure the code below treats available without the soft tail.
110
204
size_t soft_tail = max_capacity - capacity;
111
205
available = (available > soft_tail) ? (available - soft_tail) : 0 ;
112
206
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
+
115
211
size_t min_threshold = capacity / 100 * ShenandoahMinFreeThreshold;
116
212
if (available < min_threshold) {
117
213
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 {
120
216
return true ;
121
217
}
122
218
123
- // Check if are need to learn a bit about the application
124
219
const size_t max_learn = ShenandoahLearningSteps;
125
220
if (_gc_times_learned < max_learn) {
126
221
size_t init_threshold = capacity / 100 * ShenandoahInitFreeThreshold;
@@ -136,7 +231,6 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() const {
136
231
// Check if allocation headroom is still okay. This also factors in:
137
232
// 1. Some space to absorb allocation spikes
138
233
// 2. Accumulated penalties from Degenerated and Full GC
139
-
140
234
size_t allocation_headroom = available;
141
235
142
236
size_t spike_headroom = capacity / 100 * ShenandoahAllocSpikeFactor;
@@ -145,24 +239,127 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() const {
145
239
allocation_headroom -= MIN2 (allocation_headroom, spike_headroom);
146
240
allocation_headroom -= MIN2 (allocation_headroom, penalties);
147
241
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);
153
250
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));
159
251
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;
164
269
return true ;
165
270
}
166
271
167
272
return ShenandoahHeuristics::should_start_gc ();
168
273
}
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
+ }
0 commit comments