mirror of
https://github.com/openjdk/jdk.git
synced 2026-06-06 18:53:37 +00:00
8383892: Shenandoah: Decouple allocation rate sampling from GC cycle
Reviewed-by: kdnilsen, ruili, serb
This commit is contained in:
parent
153257f296
commit
df8ce1f50b
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved.
|
||||
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
@ -27,71 +27,11 @@
|
||||
#define SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHADAPTIVEHEURISTICS_HPP
|
||||
|
||||
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahFreeSet.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRate.hpp"
|
||||
#include "gc/shenandoah/shenandoahCycleDuration.hpp"
|
||||
#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
|
||||
#include "gc/shenandoah/shenandoahRegulatorThread.hpp"
|
||||
#include "gc/shenandoah/shenandoahSharedVariables.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "utilities/numberSeq.hpp"
|
||||
|
||||
/**
|
||||
* ShenandoahAllocationRate maintains a truncated history of recently sampled allocation rates for the purpose of providing
|
||||
* informed estimates of current and future allocation rates based on weighted averages and standard deviations of the
|
||||
* truncated history. More recently sampled allocations are weighted more heavily than older samples when computing
|
||||
* averages and standard deviations.
|
||||
*/
|
||||
class ShenandoahAllocationRate : public CHeapObj<mtGC> {
|
||||
public:
|
||||
explicit ShenandoahAllocationRate();
|
||||
|
||||
// Reset the _last_sample_value to zero, _last_sample_time to current time.
|
||||
void allocation_counter_reset();
|
||||
|
||||
// Force an allocation rate sample to be taken, even if the time since last sample is not greater than
|
||||
// 1s/ShenandoahAdaptiveSampleFrequencyHz, except when current_time - _last_sample_time < MinSampleTime (2 ms).
|
||||
// The sampled allocation rate is computed from (allocated - _last_sample_value) / (current_time - _last_sample_time).
|
||||
// Return the newly computed rate if the sample is taken, zero if it is not an appropriate time to add a sample.
|
||||
// In the case that a new sample is not taken, overwrite unaccounted_bytes_allocated with bytes allocated since
|
||||
// the previous sample was taken (allocated - _last_sample_value). Otherwise, overwrite unaccounted_bytes_allocated
|
||||
// with 0.
|
||||
double force_sample(size_t allocated, size_t &unaccounted_bytes_allocated);
|
||||
|
||||
// Add an allocation rate sample if the time since last sample is greater than 1s/ShenandoahAdaptiveSampleFrequencyHz.
|
||||
// The sampled allocation rate is computed from (allocated - _last_sample_value) / (current_time - _last_sample_time).
|
||||
// Return the newly computed rate if the sample is taken, zero if it is not an appropriate time to add a sample.
|
||||
double sample(size_t allocated);
|
||||
|
||||
// Return an estimate of the upper bound on allocation rate, with the upper bound computed as the weighted average
|
||||
// of recently sampled instantaneous allocation rates added to sds times the standard deviation computed for the
|
||||
// sequence of recently sampled average allocation rates.
|
||||
double upper_bound(double sds) const;
|
||||
|
||||
// Test whether rate significantly diverges from the computed average allocation rate. If so, return true.
|
||||
// Otherwise, return false. Significant divergence is recognized if (rate - _rate.avg()) / _rate.sd() > threshold.
|
||||
bool is_spiking(double rate, double threshold) const;
|
||||
|
||||
private:
|
||||
|
||||
// Return the instantaneous rate calculated from (allocated - _last_sample_value) / (time - _last_sample_time).
|
||||
// Return Sentinel value 0.0 if (time - _last_sample_time) == 0 or if (allocated <= _last_sample_value).
|
||||
double instantaneous_rate(double time, size_t allocated) const;
|
||||
|
||||
// Time at which previous allocation rate sample was collected.
|
||||
double _last_sample_time;
|
||||
|
||||
// Bytes allocated as of the time at which previous allocation rate sample was collected.
|
||||
size_t _last_sample_value;
|
||||
|
||||
// The desired interval of time between consecutive samples of the allocation rate.
|
||||
double _interval_sec;
|
||||
|
||||
// Holds a sequence of the most recently sampled instantaneous allocation rates
|
||||
TruncatedSeq _rate;
|
||||
|
||||
// Holds a sequence of the most recently computed weighted average of allocation rates, with each weighted average
|
||||
// computed immediately after an instantaneous rate was sampled
|
||||
TruncatedSeq _rate_avg;
|
||||
};
|
||||
|
||||
/*
|
||||
* The adaptive heuristic tracks the allocation behavior and average cycle
|
||||
@ -106,38 +46,18 @@ class ShenandoahAllocationRate : public CHeapObj<mtGC> {
|
||||
*/
|
||||
class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
|
||||
public:
|
||||
ShenandoahAdaptiveHeuristics(ShenandoahSpaceInfo* space_info);
|
||||
explicit ShenandoahAdaptiveHeuristics(ShenandoahSpaceInfo* space_info);
|
||||
|
||||
virtual ~ShenandoahAdaptiveHeuristics();
|
||||
void initialize() override;
|
||||
|
||||
virtual void initialize() override;
|
||||
|
||||
virtual void post_initialize() override;
|
||||
|
||||
virtual void adjust_penalty(intx step) override;
|
||||
void post_initialize() override;
|
||||
|
||||
// At the end of GC(N), we idle GC until necessary to start the next GC. Compute the threshold of memory that can be allocated
|
||||
// before we need to start the next GC.
|
||||
void start_idle_span() override;
|
||||
|
||||
// Having observed a new allocation rate sample, add this to the acceleration history so that we can determine if allocation
|
||||
// rate is accelerating.
|
||||
void add_rate_to_acceleration_history(double timestamp, double rate);
|
||||
|
||||
// Compute and return the current allocation rate, the current rate of acceleration, and the amount of memory that we expect
|
||||
// to consume if we start GC right now and gc takes predicted_cycle_time to complete.
|
||||
size_t accelerated_consumption(double& acceleration, double& current_rate,
|
||||
double avg_rate_words_per_sec, double predicted_cycle_time) const;
|
||||
|
||||
|
||||
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
|
||||
RegionData* data, size_t size,
|
||||
size_t actual_free) override;
|
||||
|
||||
void record_cycle_start() override;
|
||||
void record_success_concurrent() override;
|
||||
void record_degenerated() override;
|
||||
void record_success_full() override;
|
||||
|
||||
bool should_start_gc() override;
|
||||
|
||||
@ -145,47 +65,33 @@ public:
|
||||
bool is_diagnostic() override { return false; }
|
||||
bool is_experimental() override { return false; }
|
||||
|
||||
// In preparation for a span during which GC will be idle, compute the headroom adjustment that will be used to
|
||||
// detect when GC needs to trigger.
|
||||
void compute_headroom_adjustment() override;
|
||||
|
||||
private:
|
||||
// These are used to adjust the margin of error and the spike threshold
|
||||
// in response to GC cycle outcomes. These values are shared, but the
|
||||
// margin of error and spike threshold trend in opposite directions.
|
||||
const static double FULL_PENALTY_SD;
|
||||
const static double DEGENERATE_PENALTY_SD;
|
||||
|
||||
const static double MINIMUM_CONFIDENCE;
|
||||
const static double MAXIMUM_CONFIDENCE;
|
||||
|
||||
const static double LOWEST_EXPECTED_AVAILABLE_AT_END;
|
||||
const static double HIGHEST_EXPECTED_AVAILABLE_AT_END;
|
||||
|
||||
const static size_t GC_TIME_SAMPLE_SIZE;
|
||||
|
||||
friend class ShenandoahAllocationRate;
|
||||
|
||||
// Used to record the last trigger that signaled to start a GC.
|
||||
// This itself is used to decide whether or not to adjust the margin of
|
||||
// error for the average cycle time and allocation rate or the allocation
|
||||
// spike detection threshold.
|
||||
enum Trigger {
|
||||
SPIKE, RATE, OTHER
|
||||
};
|
||||
|
||||
void adjust_last_trigger_parameters(double amount);
|
||||
void adjust_margin_of_error(double amount);
|
||||
void adjust_spike_threshold(double amount);
|
||||
|
||||
// Returns number of words that can be allocated before we need to trigger next GC, given available in bytes.
|
||||
inline size_t allocatable(size_t available) const {
|
||||
return (available > _headroom_adjustment)? (available - _headroom_adjustment) / HeapWordSize: 0;
|
||||
// Returns number of bytes that can be allocated before we need to trigger next GC, given available in bytes.
|
||||
size_t allocatable(size_t available) const {
|
||||
return available > _headroom_adjustment ? available - _headroom_adjustment : 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
ShenandoahAllocationRate _allocation_rate;
|
||||
void adjust_penalty(intx step) override;
|
||||
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
|
||||
RegionData* data, size_t size,
|
||||
size_t actual_free) override;
|
||||
|
||||
// Invocations of should_start_gc() happen approximately once per ms. Queries of allocation rate only happen if a
|
||||
// a certain amount of time has passed since the previous query.
|
||||
size_t _allocated_at_previous_query;
|
||||
double _time_of_previous_allocation_query;
|
||||
|
||||
ShenandoahCycleDuration _cycles;
|
||||
|
||||
// Used to record the last trigger that signaled to start a GC.
|
||||
// This itself is used to decide whether to adjust the margin of
|
||||
// error for the average cycle time.
|
||||
enum Trigger {
|
||||
RATE, OTHER
|
||||
};
|
||||
|
||||
// The margin of error expressed in standard deviations to add to our
|
||||
// average cycle time and allocation rate. As this value increases we
|
||||
@ -194,18 +100,9 @@ protected:
|
||||
// concurrent GCs.
|
||||
double _margin_of_error_sd;
|
||||
|
||||
// The allocation spike threshold is expressed in standard deviations.
|
||||
// If the standard deviation of the most recent sample of the allocation
|
||||
// rate exceeds this threshold, a GC cycle is started. As this value
|
||||
// decreases the sensitivity to allocation spikes increases. In other
|
||||
// words, lowering the spike threshold will tend to increase the number
|
||||
// of concurrent GCs.
|
||||
double _spike_threshold_sd;
|
||||
|
||||
// Remember which trigger is responsible for the last GC cycle. When the
|
||||
// outcome of the cycle is evaluated we will adjust the parameters for the
|
||||
// corresponding triggers. Note that successful outcomes will raise
|
||||
// the spike threshold and lower the margin of error.
|
||||
// corresponding triggers.
|
||||
Trigger _last_trigger;
|
||||
|
||||
// Keep track of the available memory at the end of a GC cycle. This
|
||||
@ -213,67 +110,29 @@ protected:
|
||||
// source of feedback to adjust trigger parameters.
|
||||
TruncatedSeq _available;
|
||||
|
||||
ShenandoahFreeSet* _free_set;
|
||||
|
||||
// This represents the time at which the allocation rate was most recently sampled for the purpose of detecting acceleration.
|
||||
double _previous_acceleration_sample_timestamp;
|
||||
size_t _total_allocations_at_start_of_idle;
|
||||
|
||||
// bytes of headroom at which we should trigger GC
|
||||
size_t _headroom_adjustment;
|
||||
|
||||
// Keep track of GC_TIME_SAMPLE_SIZE most recent concurrent GC cycle times
|
||||
uint _gc_time_first_sample_index;
|
||||
uint _gc_time_num_samples;
|
||||
double* const _gc_time_timestamps;
|
||||
double* const _gc_time_samples;
|
||||
double* const _gc_time_xy; // timestamp * sample
|
||||
double* const _gc_time_xx; // timestamp squared
|
||||
double _gc_time_sum_of_timestamps;
|
||||
double _gc_time_sum_of_samples;
|
||||
double _gc_time_sum_of_xy;
|
||||
double _gc_time_sum_of_xx;
|
||||
|
||||
double _gc_time_m; // slope
|
||||
double _gc_time_b; // y-intercept
|
||||
double _gc_time_sd; // sd on deviance from prediction
|
||||
|
||||
// In preparation for a span during which GC will be idle, compute the headroom adjustment that will be used to
|
||||
// detect when GC needs to trigger.
|
||||
void compute_headroom_adjustment() override;
|
||||
|
||||
void add_gc_time(double timestamp_at_start, double duration);
|
||||
void add_degenerated_gc_time(double timestamp_at_start, double duration);
|
||||
double predict_gc_time(double timestamp_at_start);
|
||||
|
||||
// Keep track of SPIKE_ACCELERATION_SAMPLE_SIZE most recent spike allocation rate measurements. Note that it is
|
||||
// typical to experience a small spike following end of GC cycle, as mutator threads refresh their TLABs. But
|
||||
// there is generally an abundance of memory at this time as well, so this will not generally trigger GC.
|
||||
uint _spike_acceleration_buffer_size;
|
||||
uint _spike_acceleration_first_sample_index;
|
||||
uint _spike_acceleration_num_samples;
|
||||
double* const _spike_acceleration_rate_samples; // holds rates in words/second
|
||||
double* const _spike_acceleration_rate_timestamps;
|
||||
|
||||
// A conservative minimum threshold of free space that we'll try to maintain when possible.
|
||||
// For example, we might trigger a concurrent gc if we are likely to drop below
|
||||
// this threshold, or we might consider this when dynamically resizing generations
|
||||
// in the generational case. Controlled by global flag ShenandoahMinFreeThreshold.
|
||||
size_t min_free_threshold();
|
||||
size_t min_free_threshold(size_t capacity) const;
|
||||
|
||||
void accept_trigger_with_type(Trigger trigger_type) {
|
||||
_last_trigger = trigger_type;
|
||||
ShenandoahHeuristics::accept_trigger();
|
||||
accept_trigger();
|
||||
}
|
||||
|
||||
public:
|
||||
// Sample the allocation rate at GC trigger time if possible. Return the number of allocated bytes that were
|
||||
// not accounted for in the sample. This must be called before resetting bytes allocated since gc start.
|
||||
size_t force_alloc_rate_sample(size_t bytes_allocated) override {
|
||||
size_t unaccounted_bytes;
|
||||
_allocation_rate.force_sample(bytes_allocated, unaccounted_bytes);
|
||||
return unaccounted_bytes;
|
||||
}
|
||||
bool trigger_min_free_threshold(size_t available, size_t capacity);
|
||||
bool trigger_learning(size_t available, size_t capacity);
|
||||
bool trigger_average_allocation_rate(const ShenandoahAnticipatedConsumption& rate, size_t allocatable_bytes);
|
||||
bool trigger_accelerating_allocation_rate(const ShenandoahAnticipatedConsumption& rate, size_t allocatable_bytes);
|
||||
|
||||
private:
|
||||
void maybe_log_rate_trigger_parameters(const ShenandoahAnticipatedConsumption & consumption, size_t allocatable_bytes) const;
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHADAPTIVEHEURISTICS_HPP
|
||||
|
||||
@ -23,11 +23,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "logging/logTag.hpp"
|
||||
|
||||
@ -46,29 +46,27 @@ ShenandoahCompactHeuristics::ShenandoahCompactHeuristics(ShenandoahSpaceInfo* sp
|
||||
}
|
||||
|
||||
bool ShenandoahCompactHeuristics::should_start_gc() {
|
||||
size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
|
||||
size_t available = _space_info->soft_mutator_available();
|
||||
size_t bytes_allocated = _space_info->bytes_allocated_since_gc_start();
|
||||
const size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
|
||||
const size_t available = _space_info->soft_mutator_available();
|
||||
const size_t bytes_allocated = estimate_bytes_allocated_since_gc_start();
|
||||
|
||||
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT ", "
|
||||
"allocated_since_gc_start: " PROPERFMT,
|
||||
PROPERFMTARGS(available), PROPERFMTARGS(capacity), PROPERFMTARGS(bytes_allocated));
|
||||
|
||||
size_t threshold_bytes_allocated = capacity / 100 * ShenandoahAllocationThreshold;
|
||||
size_t min_threshold = capacity / 100 * ShenandoahMinFreeThreshold;
|
||||
const size_t threshold_bytes_allocated = capacity / 100 * ShenandoahAllocationThreshold;
|
||||
const size_t min_threshold = capacity / 100 * ShenandoahMinFreeThreshold;
|
||||
|
||||
if (available < min_threshold) {
|
||||
log_trigger("Free (Soft) (%zu%s) is below minimum threshold (%zu%s)",
|
||||
byte_size_in_proper_unit(available), proper_unit_for_byte_size(available),
|
||||
byte_size_in_proper_unit(min_threshold), proper_unit_for_byte_size(min_threshold));
|
||||
log_trigger("Free (Soft) (" PROPERFMT ") is below minimum threshold (" PROPERFMT ")",
|
||||
PROPERFMTARGS(available), PROPERFMTARGS(min_threshold));
|
||||
accept_trigger();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bytes_allocated > threshold_bytes_allocated) {
|
||||
log_trigger("Allocated since last cycle (%zu%s) is larger than allocation threshold (%zu%s)",
|
||||
byte_size_in_proper_unit(bytes_allocated), proper_unit_for_byte_size(bytes_allocated),
|
||||
byte_size_in_proper_unit(threshold_bytes_allocated), proper_unit_for_byte_size(threshold_bytes_allocated));
|
||||
log_trigger("Allocated since last cycle started (" PROPERFMT ") is larger than allocation threshold (" PROPERFMT ")",
|
||||
PROPERFMTARGS(bytes_allocated), PROPERFMTARGS(threshold_bytes_allocated));
|
||||
accept_trigger();
|
||||
return true;
|
||||
}
|
||||
@ -80,21 +78,27 @@ void ShenandoahCompactHeuristics::choose_collection_set_from_regiondata(Shenando
|
||||
RegionData* data, size_t size,
|
||||
size_t actual_free) {
|
||||
// Do not select too large CSet that would overflow the available free space
|
||||
size_t max_cset = actual_free * 3 / 4;
|
||||
const size_t max_cset = actual_free * 3 / 4;
|
||||
|
||||
log_info(gc, ergo)("CSet Selection. Actual Free: %zu%s, Max CSet: %zu%s",
|
||||
byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free),
|
||||
byte_size_in_proper_unit(max_cset), proper_unit_for_byte_size(max_cset));
|
||||
|
||||
size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
|
||||
log_info(gc, ergo)("CSet Selection. Actual Free: " PROPERFMT ", Max CSet: " PROPERFMT,
|
||||
PROPERFMTARGS(actual_free), PROPERFMTARGS(max_cset));
|
||||
|
||||
const size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
|
||||
size_t live_cset = 0;
|
||||
for (size_t idx = 0; idx < size; idx++) {
|
||||
ShenandoahHeapRegion* r = data[idx].get_region();
|
||||
size_t new_cset = live_cset + r->get_live_data_bytes();
|
||||
const size_t new_cset = live_cset + r->get_live_data_bytes();
|
||||
if (new_cset < max_cset && r->garbage() > threshold) {
|
||||
live_cset = new_cset;
|
||||
cset->add_region(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t ShenandoahCompactHeuristics::estimate_bytes_allocated_since_gc_start() const {
|
||||
ShenandoahHeap* heap = ShenandoahHeap::heap();
|
||||
const double average_allocation_rate = heap->alloc_rate().weighted_average();
|
||||
const double now = os::elapsedTime();
|
||||
const double elapsed_seconds = now - cycle_start_time_seconds();
|
||||
return shenandoah_safe_size_cast(average_allocation_rate * elapsed_seconds);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved.
|
||||
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -36,14 +37,17 @@ public:
|
||||
explicit ShenandoahCompactHeuristics(ShenandoahSpaceInfo* space_info);
|
||||
|
||||
bool should_start_gc() override;
|
||||
const char* name() override { return "Compact"; }
|
||||
bool is_diagnostic() override { return false; }
|
||||
bool is_experimental() override { return false; }
|
||||
|
||||
protected:
|
||||
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
|
||||
RegionData* data, size_t size,
|
||||
size_t actual_free) override;
|
||||
|
||||
const char* name() override { return "Compact"; }
|
||||
bool is_diagnostic() override { return false; }
|
||||
bool is_experimental() override { return false; }
|
||||
private:
|
||||
size_t estimate_bytes_allocated_since_gc_start() const;
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHCOMPACTHEURISTICS_HPP
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
|
||||
#include "gc/shenandoah/shenandoahGeneration.hpp"
|
||||
@ -32,6 +33,7 @@
|
||||
#include "gc/shenandoah/shenandoahInPlacePromoter.hpp"
|
||||
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahTrace.hpp"
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "utilities/quickSort.hpp"
|
||||
@ -48,10 +50,16 @@ static int compare_by_aged_live(AgedRegionData a, AgedRegionData b) {
|
||||
|
||||
void ShenandoahGenerationalHeuristics::post_initialize() {
|
||||
ShenandoahHeuristics::post_initialize();
|
||||
_free_set = ShenandoahHeap::heap()->free_set();
|
||||
compute_headroom_adjustment();
|
||||
}
|
||||
|
||||
void ShenandoahGenerationalHeuristics::record_cycle_end() {
|
||||
ShenandoahAdaptiveHeuristics::record_cycle_end();
|
||||
|
||||
ShenandoahAllocationRate& alloc_rate = ShenandoahHeap::heap()->alloc_rate();
|
||||
alloc_rate.update_minimum_sample_size(_space_info->soft_mutator_available());
|
||||
}
|
||||
|
||||
inline void assert_no_in_place_promotions() {
|
||||
#ifdef ASSERT
|
||||
class ShenandoahNoInPlacePromotions : public ShenandoahHeapRegionClosure {
|
||||
@ -373,7 +381,7 @@ void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap*
|
||||
ShenandoahYoungGeneration* const young_generation = heap->young_generation();
|
||||
|
||||
const size_t old_evacuated = collection_set->get_live_bytes_in_old_regions();
|
||||
size_t old_evacuated_committed = (size_t) (ShenandoahOldEvacWaste * double(old_evacuated));
|
||||
size_t old_evacuated_committed = shenandoah_safe_size_cast(ShenandoahOldEvacWaste * static_cast<double>(old_evacuated));
|
||||
size_t old_evacuation_reserve = old_generation->get_evacuation_reserve();
|
||||
|
||||
if (old_evacuated_committed > old_evacuation_reserve) {
|
||||
@ -391,11 +399,11 @@ void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap*
|
||||
old_generation->set_evacuation_reserve(old_evacuation_reserve);
|
||||
}
|
||||
|
||||
size_t young_advance_promoted = collection_set->get_live_bytes_in_tenurable_regions();
|
||||
size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted));
|
||||
const double young_advance_promoted = collection_set->get_live_bytes_in_tenurable_regions();
|
||||
size_t young_advance_promoted_reserve_used = shenandoah_safe_size_cast(ShenandoahPromoEvacWaste * young_advance_promoted);
|
||||
|
||||
size_t young_evacuated = collection_set->get_live_bytes_in_untenurable_regions();
|
||||
size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated));
|
||||
const double young_evacuated = collection_set->get_live_bytes_in_untenurable_regions();
|
||||
const size_t young_evacuated_reserve_used = shenandoah_safe_size_cast(ShenandoahEvacWaste * young_evacuated);
|
||||
|
||||
// In top_off_collection_set(), we shrunk planned future reserve by _add_regions_to_old * region_size_bytes, but we
|
||||
// didn't shrink available. The current reserve is not affected by the planned future reserve. Current available is
|
||||
|
||||
@ -55,6 +55,9 @@ public:
|
||||
|
||||
void post_initialize() override;
|
||||
|
||||
void record_cycle_end() override;
|
||||
|
||||
protected:
|
||||
// Wraps budget computation, subclass region selection, budget adjustment, and tracing.
|
||||
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set,
|
||||
RegionData* data, size_t data_size,
|
||||
|
||||
@ -25,10 +25,11 @@
|
||||
|
||||
#include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahAsserts.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
|
||||
#include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahGlobalGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
#include "utilities/quickSort.hpp"
|
||||
|
||||
bool ShenandoahEvacuationBudget::try_reserve(size_t bytes) {
|
||||
@ -248,17 +249,17 @@ void ShenandoahGlobalCSetBudget::assert_budget_constraints_hold(size_t original_
|
||||
assert(young_evac.live_bytes() * young_evac.waste_factor() <=
|
||||
young_evac.reserve() + young_evac.region_count(),
|
||||
"Young evac consumption (%zu) exceeds reserve (%zu) + region count (%zu)",
|
||||
(size_t)(young_evac.live_bytes() * young_evac.waste_factor()),
|
||||
shenandoah_safe_size_cast(young_evac.live_bytes() * young_evac.waste_factor()),
|
||||
young_evac.reserve(), young_evac.region_count());
|
||||
assert(old_evac.live_bytes() * old_evac.waste_factor() <=
|
||||
old_evac.reserve() + old_evac.region_count(),
|
||||
"Old evac consumption (%zu) exceeds reserve (%zu) + region count (%zu)",
|
||||
(size_t)(old_evac.live_bytes() * old_evac.waste_factor()),
|
||||
shenandoah_safe_size_cast(old_evac.live_bytes() * old_evac.waste_factor()),
|
||||
old_evac.reserve(), old_evac.region_count());
|
||||
assert(promo.live_bytes() * promo.waste_factor() <=
|
||||
promo.reserve() + promo.region_count(),
|
||||
"Promo consumption (%zu) exceeds reserve (%zu) + region count (%zu)",
|
||||
(size_t)(promo.live_bytes() * promo.waste_factor()),
|
||||
shenandoah_safe_size_cast(promo.live_bytes() * promo.waste_factor()),
|
||||
promo.reserve(), promo.region_count());
|
||||
|
||||
size_t total_post_reserves = young_evac.reserve() + old_evac.reserve() + promo.reserve();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2020, Red Hat, Inc. All rights reserved.
|
||||
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
@ -26,10 +26,12 @@
|
||||
|
||||
#include "gc/shared/gcCause.hpp"
|
||||
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahMarkingContext.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahTrace.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "logging/logTag.hpp"
|
||||
#include "runtime/globals_extension.hpp"
|
||||
@ -60,7 +62,6 @@ ShenandoahHeuristics::ShenandoahHeuristics(ShenandoahSpaceInfo* space_info) :
|
||||
_last_cycle_end(0),
|
||||
_gc_times_learned(0),
|
||||
_gc_time_penalties(0),
|
||||
_gc_cycle_time_history(new TruncatedSeq(Moving_Average_Samples, ShenandoahAdaptiveDecayFactor)),
|
||||
_metaspace_oom()
|
||||
{
|
||||
size_t num_regions = ShenandoahHeap::heap()->num_regions();
|
||||
@ -174,6 +175,12 @@ void ShenandoahHeuristics::record_cycle_start() {
|
||||
|
||||
void ShenandoahHeuristics::record_cycle_end() {
|
||||
_last_cycle_end = os::elapsedTime();
|
||||
|
||||
ShenandoahHeap* heap = ShenandoahHeap::heap();
|
||||
if (!heap->mode()->is_generational()) {
|
||||
const size_t available = _space_info->soft_mutator_available();
|
||||
heap->alloc_rate().update_minimum_sample_size(available);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShenandoahHeuristics::should_start_gc() {
|
||||
@ -247,7 +254,6 @@ void ShenandoahHeuristics::log_trigger(const char* fmt, ...) {
|
||||
}
|
||||
|
||||
void ShenandoahHeuristics::record_success_concurrent() {
|
||||
_gc_cycle_time_history->add(elapsed_cycle_time());
|
||||
_gc_times_learned++;
|
||||
|
||||
adjust_penalty(Concurrent_Adjust);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved.
|
||||
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
@ -83,7 +83,7 @@ private:
|
||||
double _most_recent_planned_sleep_interval;
|
||||
|
||||
protected:
|
||||
static const uint Moving_Average_Samples = 10; // Number of samples to store in moving averages
|
||||
static constexpr uint Moving_Average_Samples = 10; // Number of samples to store in moving averages
|
||||
|
||||
bool _start_gc_is_pending; // True denotes that GC has been triggered, so no need to trigger again.
|
||||
size_t _declined_trigger_count; // This counts how many times since previous GC finished that this
|
||||
@ -181,7 +181,6 @@ protected:
|
||||
|
||||
size_t _gc_times_learned;
|
||||
intx _gc_time_penalties;
|
||||
TruncatedSeq* _gc_cycle_time_history;
|
||||
|
||||
// There may be many threads that contend to set this flag
|
||||
ShenandoahSharedFlag _metaspace_oom;
|
||||
@ -230,6 +229,10 @@ public:
|
||||
// Default implementation does nothing.
|
||||
}
|
||||
|
||||
double cycle_start_time_seconds() const {
|
||||
return _cycle_start;
|
||||
}
|
||||
|
||||
virtual void record_cycle_start();
|
||||
|
||||
void record_degenerated_cycle_start(bool out_of_cycle);
|
||||
@ -278,11 +281,6 @@ public:
|
||||
double elapsed_cycle_time() const;
|
||||
double elapsed_degenerated_cycle_time() const;
|
||||
|
||||
virtual size_t force_alloc_rate_sample(size_t bytes_allocated) {
|
||||
// do nothing
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Format prefix and emit log message indicating a GC cycle hs been triggered
|
||||
void log_trigger(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3);
|
||||
|
||||
|
||||
@ -44,11 +44,6 @@ public:
|
||||
virtual size_t available() const = 0;
|
||||
virtual size_t used() const = 0;
|
||||
|
||||
// Return an approximation of the bytes allocated since GC start. The value returned is monotonically non-decreasing
|
||||
// in time within each GC cycle. For certain GC cycles, the value returned may include some bytes allocated before
|
||||
// the start of the current GC cycle.
|
||||
virtual size_t bytes_allocated_since_gc_start() const = 0;
|
||||
|
||||
// Return true if this region belongs to this space.
|
||||
virtual bool contains(ShenandoahHeapRegion* region) const = 0;
|
||||
};
|
||||
|
||||
@ -40,14 +40,11 @@ ShenandoahStaticHeuristics::ShenandoahStaticHeuristics(ShenandoahSpaceInfo* spac
|
||||
bool ShenandoahStaticHeuristics::should_start_gc() {
|
||||
size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
|
||||
size_t available = _space_info->soft_mutator_available();
|
||||
size_t allocated = _space_info->bytes_allocated_since_gc_start();
|
||||
|
||||
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT ", "
|
||||
"allocated_since_gc_start: " PROPERFMT,
|
||||
PROPERFMTARGS(available), PROPERFMTARGS(capacity), PROPERFMTARGS(allocated));
|
||||
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT,
|
||||
PROPERFMTARGS(available), PROPERFMTARGS(capacity));
|
||||
|
||||
size_t threshold_available = capacity / 100 * ShenandoahMinFreeThreshold;
|
||||
|
||||
if (available < threshold_available) {
|
||||
log_trigger("Free (Soft) (" PROPERFMT ") is below minimum threshold (" PROPERFMT ")",
|
||||
PROPERFMTARGS(available), PROPERFMTARGS(threshold_available));
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,13 +23,17 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gc/shared/gc_globals.hpp"
|
||||
#include "gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp"
|
||||
#include "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp"
|
||||
#include "gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeap.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/quickSort.hpp"
|
||||
|
||||
ShenandoahYoungHeuristics::ShenandoahYoungHeuristics(ShenandoahYoungGeneration* generation)
|
||||
@ -80,7 +84,7 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
|
||||
// If this is mixed evacuation, the old-gen candidate regions have already been added.
|
||||
size_t cur_cset = 0;
|
||||
size_t cur_young_garbage = cset->garbage();
|
||||
const size_t max_cset = (size_t) (heap->young_generation()->get_evacuation_reserve() / ShenandoahEvacWaste);
|
||||
const size_t max_cset = shenandoah_safe_size_cast(heap->young_generation()->get_evacuation_reserve() / ShenandoahEvacWaste);
|
||||
const size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + max_cset;
|
||||
const size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0;
|
||||
|
||||
@ -113,38 +117,24 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ShenandoahYoungHeuristics::should_start_gc() {
|
||||
auto heap = ShenandoahGenerationalHeap::heap();
|
||||
ShenandoahOldGeneration* old_generation = heap->old_generation();
|
||||
ShenandoahOldHeuristics* old_heuristics = old_generation->heuristics();
|
||||
|
||||
// Checks that an old cycle has run for at least ShenandoahMinimumOldTimeMs before allowing a young cycle.
|
||||
bool ShenandoahYoungHeuristics::old_collection_needs_more_time(ShenandoahOldGeneration* old_generation, ShenandoahOldHeuristics* old_heuristics) {
|
||||
if (ShenandoahMinimumOldTimeMs > 0) {
|
||||
if (old_generation->is_preparing_for_mark() || old_generation->is_concurrent_mark_in_progress()) {
|
||||
size_t old_time_elapsed = size_t(old_heuristics->elapsed_cycle_time() * 1000);
|
||||
if (old_time_elapsed < ShenandoahMinimumOldTimeMs) {
|
||||
// Do not decline_trigger() when waiting for minimum quantum of Old-gen marking. It is not at our discretion
|
||||
// to trigger at this time.
|
||||
log_debug(gc)("Young heuristics declines to trigger because old_time_elapsed < ShenandoahMinimumOldTimeMs");
|
||||
return false;
|
||||
}
|
||||
const auto old_time_elapsed = shenandoah_safe_size_cast(old_heuristics->elapsed_cycle_time() * 1000);
|
||||
return old_time_elapsed < ShenandoahMinimumOldTimeMs;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// inherited triggers have already decided to start a cycle, so no further evaluation is required
|
||||
if (ShenandoahAdaptiveHeuristics::should_start_gc()) {
|
||||
// ShenandoahAdaptiveHeuristics::should_start_gc() has already accepted trigger, or declined it.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShenandoahYoungHeuristics::trigger_expedite_promotions(ShenandoahGenerationalHeap* heap, ShenandoahOldGeneration* old_generation) {
|
||||
// Get through promotions and mixed evacuations as quickly as possible. These cycles sometimes require significantly
|
||||
// more time than traditional young-generation cycles so start them up as soon as possible. This is a "mitigation"
|
||||
// for the reality that old-gen and young-gen activities are not truly "concurrent". If there is old-gen work to
|
||||
// be done, we start up the young-gen GC threads so they can do some of this old-gen work. As implemented, promotion
|
||||
// gets priority over old-gen marking.
|
||||
size_t promo_expedite_threshold = percent_of(heap->young_generation()->max_capacity(), ShenandoahExpeditePromotionsThreshold);
|
||||
size_t promo_potential = old_generation->get_promotion_potential();
|
||||
const size_t promo_expedite_threshold = percent_of(heap->young_generation()->max_capacity(), ShenandoahExpeditePromotionsThreshold);
|
||||
const size_t promo_potential = old_generation->get_promotion_potential();
|
||||
if (promo_potential > promo_expedite_threshold) {
|
||||
// Detect unsigned arithmetic underflow
|
||||
assert(promo_potential < heap->capacity(), "Sanity");
|
||||
@ -152,8 +142,11 @@ bool ShenandoahYoungHeuristics::should_start_gc() {
|
||||
accept_trigger();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t mixed_candidates = old_heuristics->unprocessed_old_collection_candidates();
|
||||
bool ShenandoahYoungHeuristics::trigger_expedite_mixed(ShenandoahGenerationalHeap* heap, ShenandoahOldHeuristics* old_heuristics) {
|
||||
const size_t mixed_candidates = old_heuristics->unprocessed_old_collection_candidates();
|
||||
if (mixed_candidates > ShenandoahExpediteMixedThreshold && !heap->is_concurrent_weak_root_in_progress()) {
|
||||
// We need to run young GC in order to open up some free heap regions so we can finish mixed evacuations.
|
||||
// If concurrent weak root processing is in progress, it means the old cycle has chosen mixed collection
|
||||
@ -163,6 +156,33 @@ bool ShenandoahYoungHeuristics::should_start_gc() {
|
||||
accept_trigger();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShenandoahYoungHeuristics::should_start_gc() {
|
||||
auto heap = ShenandoahGenerationalHeap::heap();
|
||||
ShenandoahOldGeneration* old_generation = heap->old_generation();
|
||||
ShenandoahOldHeuristics* old_heuristics = old_generation->heuristics();
|
||||
|
||||
// Checks that an old cycle has run for at least ShenandoahMinimumOldTimeMs before allowing a young cycle.
|
||||
if (old_collection_needs_more_time(old_generation, old_heuristics)) {
|
||||
log_debug(gc)("Young heuristics declines to trigger because old_time_elapsed < ShenandoahMinimumOldTimeMs");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ShenandoahAdaptiveHeuristics::should_start_gc()) {
|
||||
// Inherited triggers have already decided to start a cycle, so no further evaluation is required
|
||||
// ShenandoahAdaptiveHeuristics::should_start_gc() has already accepted trigger, or declined it.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trigger_expedite_promotions(heap, old_generation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trigger_expedite_mixed(heap, old_heuristics)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't decline_trigger() here That was done in ShenandoahAdaptiveHeuristics::should_start_gc()
|
||||
return false;
|
||||
@ -173,20 +193,16 @@ bool ShenandoahYoungHeuristics::should_start_gc() {
|
||||
// generation at the end of the current cycle (as represented by young_regions_to_be_reclaimed) and on the anticipated
|
||||
// amount of time required to perform a GC.
|
||||
size_t ShenandoahYoungHeuristics::bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed) {
|
||||
size_t capacity = _space_info->max_capacity();
|
||||
size_t usage = _space_info->used();
|
||||
size_t available = (capacity > usage)? capacity - usage: 0;
|
||||
size_t allocated = _free_set->get_bytes_allocated_since_gc_start();
|
||||
size_t anticipated_available = available + young_regions_to_be_reclaimed * ShenandoahHeapRegion::region_size_bytes();
|
||||
const size_t capacity = _space_info->max_capacity();
|
||||
const size_t usage = _space_info->used();
|
||||
const size_t available = (capacity > usage) ? capacity - usage: 0;
|
||||
const size_t anticipated_available = available + young_regions_to_be_reclaimed * ShenandoahHeapRegion::region_size_bytes();
|
||||
|
||||
size_t spike_headroom = capacity * ShenandoahAllocSpikeFactor / 100;
|
||||
size_t penalties = capacity * _gc_time_penalties / 100;
|
||||
const size_t spike_headroom = capacity * ShenandoahAllocSpikeFactor / 100;
|
||||
const size_t penalties = capacity * _gc_time_penalties / 100;
|
||||
|
||||
double rate = _allocation_rate.sample(allocated);
|
||||
|
||||
// At what value of available, would avg and spike triggers occur?
|
||||
// At what value of available, would avg rate trigger occur?
|
||||
// if allocation_headroom < avg_cycle_time * avg_alloc_rate, then we experience avg trigger
|
||||
// if allocation_headroom < avg_cycle_time * rate, then we experience spike trigger if is_spiking
|
||||
//
|
||||
// allocation_headroom =
|
||||
// 0, if penalties > available or if penalties + spike_headroom > available
|
||||
@ -199,34 +215,19 @@ size_t ShenandoahYoungHeuristics::bytes_of_allocation_runway_before_gc_trigger(s
|
||||
// since avg_cycle_time * avg_alloc_rate > 0, the first test is sufficient to test both conditions
|
||||
//
|
||||
// thus, evac_slack_avg is MIN2(0, available - avg_cycle_time * avg_alloc_rate + penalties + spike_headroom)
|
||||
//
|
||||
// similarly, evac_slack_spiking is MIN2(0, available - avg_cycle_time * rate + penalties + spike_headroom)
|
||||
// but evac_slack_spiking is only relevant if is_spiking, as defined below.
|
||||
|
||||
double avg_cycle_time = _gc_cycle_time_history->davg() + (_margin_of_error_sd * _gc_cycle_time_history->dsd());
|
||||
double avg_alloc_rate = _allocation_rate.upper_bound(_margin_of_error_sd);
|
||||
const double avg_cycle_time = _cycles.predict_duration(os::elapsedTime(), _margin_of_error_sd);
|
||||
const double avg_alloc_rate = ShenandoahHeap::heap()->alloc_rate().upper_bound(_margin_of_error_sd);
|
||||
const double remaining_before_gc = avg_cycle_time * avg_alloc_rate + penalties + spike_headroom;
|
||||
size_t evac_slack_avg;
|
||||
if (anticipated_available > avg_cycle_time * avg_alloc_rate + penalties + spike_headroom) {
|
||||
evac_slack_avg = anticipated_available - (avg_cycle_time * avg_alloc_rate + penalties + spike_headroom);
|
||||
if (anticipated_available > remaining_before_gc) {
|
||||
evac_slack_avg = shenandoah_safe_size_cast(anticipated_available - remaining_before_gc);
|
||||
} else {
|
||||
// we have no slack because it's already time to trigger
|
||||
evac_slack_avg = 0;
|
||||
}
|
||||
|
||||
bool is_spiking = _allocation_rate.is_spiking(rate, _spike_threshold_sd);
|
||||
size_t evac_slack_spiking;
|
||||
if (is_spiking) {
|
||||
if (anticipated_available > avg_cycle_time * rate + penalties + spike_headroom) {
|
||||
evac_slack_spiking = anticipated_available - (avg_cycle_time * rate + penalties + spike_headroom);
|
||||
} else {
|
||||
// we have no slack because it's already time to trigger
|
||||
evac_slack_spiking = 0;
|
||||
}
|
||||
} else {
|
||||
evac_slack_spiking = evac_slack_avg;
|
||||
}
|
||||
|
||||
size_t threshold = min_free_threshold();
|
||||
size_t evac_min_threshold = (anticipated_available > threshold)? anticipated_available - threshold: 0;
|
||||
return MIN3(evac_slack_spiking, evac_slack_avg, evac_min_threshold);
|
||||
const size_t threshold = min_free_threshold(capacity);
|
||||
const size_t evac_min_threshold = anticipated_available > threshold ? anticipated_available - threshold : 0;
|
||||
return MIN2(evac_slack_avg, evac_min_threshold);
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
#include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp"
|
||||
|
||||
class ShenandoahYoungGeneration;
|
||||
class ShenandoahOldGeneration;
|
||||
class ShenandoahOldHeuristics;
|
||||
|
||||
/*
|
||||
* This is a specialization of the generational heuristic which chooses
|
||||
@ -37,20 +39,26 @@ class ShenandoahYoungHeuristics : public ShenandoahGenerationalHeuristics {
|
||||
public:
|
||||
explicit ShenandoahYoungHeuristics(ShenandoahYoungGeneration* generation);
|
||||
|
||||
|
||||
void select_collection_set_regions(ShenandoahCollectionSet* cset,
|
||||
RegionData* data, size_t size,
|
||||
size_t actual_free) override;
|
||||
|
||||
bool should_start_gc() override;
|
||||
|
||||
size_t bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed);
|
||||
|
||||
protected:
|
||||
void select_collection_set_regions(ShenandoahCollectionSet* cset,
|
||||
RegionData* data, size_t size,
|
||||
size_t actual_free) override;
|
||||
|
||||
private:
|
||||
void choose_young_collection_set(ShenandoahCollectionSet* cset,
|
||||
const RegionData* data,
|
||||
size_t size, size_t actual_free) const;
|
||||
|
||||
bool old_collection_needs_more_time(ShenandoahOldGeneration* old_generation,
|
||||
ShenandoahOldHeuristics* old_heuristics);
|
||||
|
||||
bool trigger_expedite_promotions(ShenandoahGenerationalHeap* heap, ShenandoahOldGeneration* old_generation);
|
||||
|
||||
bool trigger_expedite_mixed(ShenandoahGenerationalHeap* heap, ShenandoahOldHeuristics* old_heuristics);
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHYOUNGHEURISTICS_HPP
|
||||
|
||||
175
src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp
Normal file
175
src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP
|
||||
#define SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP
|
||||
|
||||
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/mutex.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "runtime/os.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
class ShenandoahAllocationClock {
|
||||
public:
|
||||
static jlong elapsed_counter() {
|
||||
return os::elapsed_counter();
|
||||
}
|
||||
|
||||
static jlong elapsed_frequency() {
|
||||
return os::elapsed_frequency();
|
||||
}
|
||||
};
|
||||
|
||||
// Snapshot values used by heuristic triggers to avoid lock contention
|
||||
struct ShenandoahAnticipatedConsumption {
|
||||
template<typename Clock> friend class ShenandoahAllocRate;
|
||||
explicit ShenandoahAnticipatedConsumption(double duration_seconds)
|
||||
: _duration_seconds(duration_seconds)
|
||||
, _baseline(0.0)
|
||||
, _momentary(0.0)
|
||||
, _acceleration(0.0)
|
||||
, _predicted_rate(0.0) {
|
||||
}
|
||||
|
||||
// Anticipated duration in seconds of next gc cycle
|
||||
double duration_seconds() const {
|
||||
return _duration_seconds;
|
||||
}
|
||||
|
||||
// Consumption in bytes based on baseline allocation rate for the next gc cycle
|
||||
size_t baseline_consumption() const;
|
||||
double baseline_rate() const {
|
||||
return _baseline;
|
||||
}
|
||||
|
||||
// Consumption in bytes based on momentary allocation rate for the next gc cycle
|
||||
size_t momentary_consumption() const;
|
||||
double momentary_rate() const {
|
||||
return _momentary;
|
||||
}
|
||||
|
||||
// Consumption in bytes based on an accelerating allocation rate for the next gc cycle
|
||||
size_t accelerated_consumption() const;
|
||||
|
||||
// The acceleration of the allocation rate (based on slope of linear regression)
|
||||
double acceleration() const {
|
||||
return _acceleration;
|
||||
}
|
||||
|
||||
// Predicated allocation rate based on weighted linear regression
|
||||
double predicted_rate() const {
|
||||
return _predicted_rate;
|
||||
}
|
||||
|
||||
private:
|
||||
double _duration_seconds;
|
||||
double _baseline;
|
||||
double _momentary;
|
||||
double _acceleration;
|
||||
double _predicted_rate;
|
||||
};
|
||||
|
||||
|
||||
// This class tracks three moving averages of the allocation rate:
|
||||
// 1. Momentary: this is the shortest and acts as a sort of 'spike' detector
|
||||
// 2. Recent: larger than momentary, these samples are used to detect 'acceleration' of the rate
|
||||
// 3. Baseline: the largest sample window, this is meant to establish the baseline allocation rate
|
||||
//
|
||||
// Samples are taken whenever the accumulating count of bytes allocated exceeds the
|
||||
// minimum sample size. The minimum sample size is generally derived from the heap
|
||||
// capacity. The thinking is that larger heaps require less frequent sampling. Note
|
||||
// that as the allocation rate increases, the timeliness of the averages and other
|
||||
// estimates increases.
|
||||
template<typename Clock = ShenandoahAllocationClock>
|
||||
class ShenandoahAllocRate {
|
||||
static constexpr size_t ALLOC_SAMPLE_PORTION = 128;
|
||||
static constexpr size_t ALLOC_SAMPLE_MIN = M;
|
||||
static constexpr size_t ALLOC_SAMPLE_MAX = G;
|
||||
|
||||
PaddedMonitor _sample_lock;
|
||||
Atomic<size_t> _allocated_bytes_since_last_sample;
|
||||
Atomic<size_t> _minimum_sample_size; // bytes, read by mutator, updated by gc
|
||||
jlong _last_sample_time;
|
||||
|
||||
ShenandoahWeightedSeq _baseline;
|
||||
ShenandoahWeightedSeq _recent;
|
||||
ShenandoahWeightedSeq _momentary;
|
||||
|
||||
public:
|
||||
explicit ShenandoahAllocRate(const uint minimum_sample_size = ALLOC_SAMPLE_MIN,
|
||||
const uint baseline_window_size = ShenandoahAllocRateSampleWindow,
|
||||
const uint recent_window_size = ShenandoahRecentAllocRateSampleWindow,
|
||||
const uint momentary_window_size = ShenandoahMomentaryAllocRateSampleWindow)
|
||||
: _sample_lock(Mutex::nosafepoint - 2, "ShenandoahAllocSample_lock", true)
|
||||
, _allocated_bytes_since_last_sample(0)
|
||||
, _minimum_sample_size(minimum_sample_size)
|
||||
, _last_sample_time(Clock::elapsed_counter())
|
||||
, _baseline(baseline_window_size)
|
||||
, _recent(recent_window_size)
|
||||
, _momentary(momentary_window_size)
|
||||
{
|
||||
}
|
||||
|
||||
// Update minimum sample size based on the given available bytes
|
||||
void update_minimum_sample_size(size_t available);
|
||||
|
||||
// Set minimum sample size in bytes
|
||||
void set_minimum_sample_size(const size_t minimum_sample_size) {
|
||||
_minimum_sample_size.store_relaxed(minimum_sample_size);
|
||||
}
|
||||
|
||||
// Indicate that this many bytes have been allocated (by the mutator).
|
||||
void allocated(size_t allocated_bytes);
|
||||
|
||||
// Returns a snapshot of the parameters necessary to evaluate allocation rate triggers.
|
||||
// Note that momentary consumption and accelerated consumption may both be zero, but may
|
||||
// not both be non-zero. The `time_delta` parameter is the anticipated duration of the
|
||||
// next gc cycle. The `standard_deviations` parameter is the margin of error applied to
|
||||
// the baseline allocation rate expressed as a multiple of the standard deviation.
|
||||
ShenandoahAnticipatedConsumption snapshot(double time_delta, double standard_deviations);
|
||||
|
||||
// Returns the weighted average of the samples.
|
||||
double weighted_average() {
|
||||
MonitorLocker locker(&_sample_lock, Mutex::_no_safepoint_check_flag);
|
||||
return _baseline.weighted_average();
|
||||
}
|
||||
|
||||
// Returns the upper bound of the confidence interval about the mean in terms of the given deviation.
|
||||
double upper_bound(const double standard_deviations) {
|
||||
MonitorLocker locker(&_sample_lock, Mutex::_no_safepoint_check_flag);
|
||||
return upper_bound_no_lock(standard_deviations);
|
||||
}
|
||||
|
||||
private:
|
||||
double upper_bound_no_lock(const double standard_deviations) const {
|
||||
assert(_sample_lock.is_locked(), "Caller must hold lock");
|
||||
return _baseline.weighted_average() + standard_deviations * _baseline.weighted_sd();
|
||||
}
|
||||
};
|
||||
|
||||
typedef ShenandoahAllocRate<> ShenandoahAllocationRate;
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP
|
||||
120
src/hotspot/share/gc/shenandoah/shenandoahAllocRate.inline.hpp
Normal file
120
src/hotspot/share/gc/shenandoah/shenandoahAllocRate.inline.hpp
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_INLINE_HPP
|
||||
#define SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_INLINE_HPP
|
||||
|
||||
#include "gc/shenandoah/shenandoahAllocRate.hpp"
|
||||
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "logging/log.hpp"
|
||||
|
||||
|
||||
inline size_t ShenandoahAnticipatedConsumption::baseline_consumption() const {
|
||||
return shenandoah_safe_size_cast(_baseline * _duration_seconds);
|
||||
}
|
||||
|
||||
inline size_t ShenandoahAnticipatedConsumption::momentary_consumption() const {
|
||||
return shenandoah_safe_size_cast(_momentary * _duration_seconds);
|
||||
}
|
||||
|
||||
inline size_t ShenandoahAnticipatedConsumption::accelerated_consumption() const {
|
||||
const double consumption = _predicted_rate * _duration_seconds + 0.5 * _acceleration * _duration_seconds * _duration_seconds;
|
||||
return shenandoah_safe_size_cast(consumption);
|
||||
}
|
||||
|
||||
template<typename Clock>
|
||||
void ShenandoahAllocRate<Clock>::update_minimum_sample_size(const size_t available) {
|
||||
const size_t min_sample_size = clamp(available / ALLOC_SAMPLE_PORTION, ALLOC_SAMPLE_MIN, ALLOC_SAMPLE_MAX);
|
||||
log_info(gc, ergo)("Adjust minimum allocation sample size to: " PROPERFMT, PROPERFMTARGS(min_sample_size));
|
||||
set_minimum_sample_size(min_sample_size);
|
||||
}
|
||||
|
||||
template<typename Clock>
|
||||
void ShenandoahAllocRate<Clock>::allocated(const size_t allocated_bytes) {
|
||||
size_t unsampled = _allocated_bytes_since_last_sample.add_then_fetch(allocated_bytes);
|
||||
const size_t minimum_sample_size = _minimum_sample_size.load_relaxed();
|
||||
if (unsampled < minimum_sample_size) {
|
||||
// Not enough to sample yet
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_sample_lock.try_lock()) {
|
||||
// Another thread has the lock and will take the sample
|
||||
return;
|
||||
}
|
||||
|
||||
unsampled = _allocated_bytes_since_last_sample.load_relaxed();
|
||||
if (unsampled < minimum_sample_size) {
|
||||
// Another thread has sampled and reset the allocated bytes under the lock
|
||||
_sample_lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const jlong now = Clock::elapsed_counter();
|
||||
const jlong elapsed = now - _last_sample_time;
|
||||
|
||||
if (elapsed <= 0) {
|
||||
// Avoid sampling nonsense allocation rates
|
||||
_sample_lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
_last_sample_time = now;
|
||||
|
||||
// We are recording this sample, deduct it from the counter. It may be increased
|
||||
// concurrently by other threads outside the lock, so we still use an atomic access.
|
||||
_allocated_bytes_since_last_sample.sub_then_fetch(unsampled);
|
||||
|
||||
const double timestamp = static_cast<double>(_last_sample_time) / Clock::elapsed_frequency();
|
||||
const double rate_seconds = static_cast<double>(unsampled) * Clock::elapsed_frequency() / elapsed;
|
||||
|
||||
_baseline.add(timestamp, rate_seconds);
|
||||
_recent.add(timestamp, rate_seconds);
|
||||
_momentary.add(timestamp, rate_seconds);
|
||||
|
||||
_sample_lock.unlock();
|
||||
|
||||
log_trace(gc, sampling)("Recorded %.3f/s at %.3fs", rate_seconds, timestamp);
|
||||
}
|
||||
|
||||
template<typename Clock>
|
||||
ShenandoahAnticipatedConsumption ShenandoahAllocRate<Clock>::snapshot(const double time_delta, const double standard_deviations) {
|
||||
ShenandoahAnticipatedConsumption result(time_delta);
|
||||
MonitorLocker locker(&_sample_lock, Mutex::_no_safepoint_check_flag);
|
||||
|
||||
result._baseline = upper_bound_no_lock(standard_deviations);
|
||||
|
||||
if (_recent.weighted_average() <= _baseline.weighted_average()) {
|
||||
// We are not accelerating, just use the momentary average.
|
||||
result._momentary = _momentary.weighted_average();
|
||||
} else {
|
||||
result._acceleration = _recent.slope();
|
||||
result._predicted_rate = _recent.predict_y(_recent.last());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_INLINE_HPP
|
||||
@ -193,7 +193,7 @@ void ShenandoahArguments::initialize() {
|
||||
// TLAB sizing policy makes resizing decisions before each GC cycle. It averages
|
||||
// historical data, assigning more recent data the weight according to TLABAllocationWeight.
|
||||
// Current default is good for generational collectors that run frequent young GCs.
|
||||
// With Shenandoah, GC cycles are much less frequent, so we need we need sizing policy
|
||||
// With Shenandoah, GC cycles are much less frequent, so we need sizing policy
|
||||
// to converge faster over smaller number of resizing decisions.
|
||||
if (strcmp(ShenandoahGCMode, "generational") && FLAG_IS_DEFAULT(TLABAllocationWeight)) {
|
||||
FLAG_SET_DEFAULT(TLABAllocationWeight, 90);
|
||||
@ -202,7 +202,7 @@ void ShenandoahArguments::initialize() {
|
||||
|
||||
if (GCCardSizeInBytes < ShenandoahMinCardSizeInBytes) {
|
||||
vm_exit_during_initialization(
|
||||
err_msg("GCCardSizeInBytes ( %u ) must be >= %u\n", GCCardSizeInBytes, (unsigned int) ShenandoahMinCardSizeInBytes));
|
||||
err_msg("GCCardSizeInBytes ( %u ) must be >= %u\n", GCCardSizeInBytes, ShenandoahMinCardSizeInBytes));
|
||||
}
|
||||
|
||||
// Gen shen does not support any ShenandoahGCHeuristics value except for the default "adaptive"
|
||||
@ -213,6 +213,16 @@ void ShenandoahArguments::initialize() {
|
||||
FLAG_SET_ERGO(ShenandoahGCHeuristics, "adaptive");
|
||||
}
|
||||
|
||||
if (ShenandoahMomentaryAllocRateSampleWindow > ShenandoahRecentAllocRateSampleWindow
|
||||
|| ShenandoahRecentAllocRateSampleWindow > ShenandoahAllocRateSampleWindow) {
|
||||
vm_exit_during_initialization(
|
||||
err_msg("Relation must hold: ShenandoahMomentaryAllocRateSampleWindow (%u) "
|
||||
"<= ShenandoahRecentAllocRateSampleWindow (%u) "
|
||||
"<= ShenandoahAllocRateSampleWindow (%u)",
|
||||
ShenandoahMomentaryAllocRateSampleWindow, ShenandoahRecentAllocRateSampleWindow,
|
||||
ShenandoahAllocRateSampleWindow));
|
||||
}
|
||||
|
||||
FullGCForwarding::initialize_flags(MaxHeapSize);
|
||||
}
|
||||
|
||||
|
||||
@ -138,11 +138,6 @@ void ShenandoahControlThread::run_service() {
|
||||
|
||||
heuristics->cancel_trigger_request();
|
||||
|
||||
if (mode != stw_degenerated) {
|
||||
// If mode is stw_degenerated, count bytes allocated from the start of the conc GC that experienced alloc failure.
|
||||
heap->reset_bytes_allocated_since_gc_start();
|
||||
}
|
||||
|
||||
MetaspaceCombinedStats meta_sizes = MetaspaceUtils::get_combined_statistics();
|
||||
|
||||
// If GC was requested, we are sampling the counters even without actual triggers
|
||||
|
||||
53
src/hotspot/share/gc/shenandoah/shenandoahCycleDuration.cpp
Normal file
53
src/hotspot/share/gc/shenandoah/shenandoahCycleDuration.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gc/shenandoah/shenandoahCycleDuration.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "logging/logTag.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
ShenandoahCycleDuration::ShenandoahCycleDuration(uint size)
|
||||
: _gc_times_lock(Mutex::nosafepoint - 2, "ShenandoahCycleTimes_lock", true)
|
||||
, _gc_times(size) {}
|
||||
|
||||
void ShenandoahCycleDuration::record_duration(double timestamp_at_start, double duration) {
|
||||
log_debug(gc, sampling)("Cycle started at: %.3f, completed in %.3fs", timestamp_at_start, duration);
|
||||
MonitorLocker locker(&_gc_times_lock, Mutex::_no_safepoint_check_flag);
|
||||
_gc_times.add(timestamp_at_start, duration);
|
||||
}
|
||||
|
||||
double ShenandoahCycleDuration::predict_duration(double timestamp_at_start, double margin_of_error) {
|
||||
MonitorLocker locker(&_gc_times_lock, Mutex::_no_safepoint_check_flag);
|
||||
|
||||
const double prediction = _gc_times.predict_y(timestamp_at_start);
|
||||
if (std::isfinite(prediction) && prediction > 0.0) {
|
||||
return prediction + _gc_times.residual_sd() * margin_of_error;
|
||||
}
|
||||
|
||||
// return average time, rather than negative or zero time
|
||||
return _gc_times.average() + _gc_times.sd() * margin_of_error;
|
||||
}
|
||||
47
src/hotspot/share/gc/shenandoah/shenandoahCycleDuration.hpp
Normal file
47
src/hotspot/share/gc/shenandoah/shenandoahCycleDuration.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHCYCLEDURATION_HPP
|
||||
#define SHARE_GC_SHENANDOAH_SHENANDOAHCYCLEDURATION_HPP
|
||||
|
||||
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
|
||||
#include "runtime/mutex.hpp"
|
||||
|
||||
class ShenandoahCycleDuration {
|
||||
// To enable detection of GC time trends, we keep separate track of the recent history of gc time. During initialization,
|
||||
// for example, the amount of live memory may be increasing, which is likely to cause the GC times to increase. This history
|
||||
// allows us to predict increasing GC times rather than always assuming average recent GC time is the best predictor.
|
||||
static constexpr uint GC_TIME_SAMPLE_SIZE = 15;
|
||||
|
||||
// Written by control thread, read by regulator thread
|
||||
Monitor _gc_times_lock;
|
||||
ShenandoahWeightedSeq _gc_times;
|
||||
|
||||
public:
|
||||
explicit ShenandoahCycleDuration(uint size = GC_TIME_SAMPLE_SIZE);
|
||||
void record_duration(double timestamp_at_start, double duration);
|
||||
double predict_duration(double timestamp_at_start, double margin_of_error);
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCYCLEDURATION_HPP
|
||||
@ -289,32 +289,6 @@ void ShenandoahFreeSet::resize_old_collector_capacity(size_t regions) {
|
||||
// else, old generation is already appropriately sized
|
||||
}
|
||||
|
||||
|
||||
void ShenandoahFreeSet::reset_bytes_allocated_since_gc_start(size_t initial_bytes_allocated) {
|
||||
shenandoah_assert_heaplocked();
|
||||
// Future inquiries of get_total_bytes_allocated() will return the sum of
|
||||
// _total_bytes_previously_allocated and _mutator_bytes_allocated_since_gc_start.
|
||||
// Since _mutator_bytes_allocated_since_gc_start does not start at zero, we subtract initial_bytes_allocated so as
|
||||
// to not double count these allocated bytes.
|
||||
size_t original_mutator_bytes_allocated_since_gc_start = _mutator_bytes_allocated_since_gc_start;
|
||||
|
||||
// Setting _mutator_bytes_allocated_since_gc_start before _total_bytes_previously_allocated reduces the damage
|
||||
// in the case that the control or regulator thread queries get_bytes_allocated_since_previous_sample() between
|
||||
// the two assignments.
|
||||
//
|
||||
// These are not declared as volatile so the compiler or hardware may reorder the assignments. The implementation of
|
||||
// get_bytes_allocated_since_previous_cycle() is robust to this possibility, as are triggering heuristics. The current
|
||||
// implementation assumes we are better off to tolerate the very rare race rather than impose a synchronization penalty
|
||||
// on every update and fetch. (Perhaps it would be better to make the opposite tradeoff for improved maintainability.)
|
||||
_mutator_bytes_allocated_since_gc_start = initial_bytes_allocated;
|
||||
_total_bytes_previously_allocated += original_mutator_bytes_allocated_since_gc_start - initial_bytes_allocated;
|
||||
}
|
||||
|
||||
void ShenandoahFreeSet::increase_bytes_allocated(size_t bytes) {
|
||||
shenandoah_assert_heaplocked();
|
||||
_mutator_bytes_allocated_since_gc_start += bytes;
|
||||
}
|
||||
|
||||
inline idx_t ShenandoahRegionPartitions::leftmost(ShenandoahFreeSetPartitionId which_partition) const {
|
||||
assert (which_partition < NumPartitions, "selected free partition must be valid");
|
||||
idx_t idx = _leftmosts[int(which_partition)];
|
||||
@ -1229,8 +1203,6 @@ inline void ShenandoahRegionPartitions::assert_bounds_sanity() {
|
||||
ShenandoahFreeSet::ShenandoahFreeSet(ShenandoahHeap* heap, size_t max_regions) :
|
||||
_heap(heap),
|
||||
_partitions(max_regions, this),
|
||||
_total_bytes_previously_allocated(0),
|
||||
_mutator_bytes_at_last_sample(0),
|
||||
_total_humongous_waste(0),
|
||||
_alloc_bias_weight(0),
|
||||
_total_young_used(0),
|
||||
@ -1242,8 +1214,7 @@ ShenandoahFreeSet::ShenandoahFreeSet(ShenandoahHeap* heap, size_t max_regions) :
|
||||
_young_unaffiliated_regions(0),
|
||||
_global_unaffiliated_regions(0),
|
||||
_total_young_regions(0),
|
||||
_total_global_regions(0),
|
||||
_mutator_bytes_allocated_since_gc_start(0)
|
||||
_total_global_regions(0)
|
||||
{
|
||||
clear_internal();
|
||||
}
|
||||
@ -1660,7 +1631,6 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah
|
||||
if (req.is_mutator_alloc()) {
|
||||
assert(req.is_young(), "Mutator allocations always come from young generation.");
|
||||
_partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, req.actual_size() * HeapWordSize);
|
||||
increase_bytes_allocated(req.actual_size() * HeapWordSize);
|
||||
} else {
|
||||
assert(req.is_gc_alloc(), "Should be gc_alloc since req wasn't mutator alloc");
|
||||
|
||||
@ -1699,7 +1669,7 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah
|
||||
size_t waste_bytes = _partitions.retire_from_partition(orig_partition, idx, r->used());
|
||||
DEBUG_ONLY(boundary_changed = true;)
|
||||
if (req.is_mutator_alloc() && (waste_bytes > 0)) {
|
||||
increase_bytes_allocated(waste_bytes);
|
||||
req.set_waste(waste_bytes / HeapWordSize);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1871,15 +1841,9 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req, bo
|
||||
}
|
||||
}
|
||||
_partitions.decrease_empty_region_counts(ShenandoahFreeSetPartitionId::Mutator, num);
|
||||
if (waste_bytes > 0) {
|
||||
// For humongous allocations, waste_bytes are included in total_used. Since this is not humongous,
|
||||
// we need to account separately for the waste_bytes.
|
||||
increase_bytes_allocated(waste_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
_partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, total_used);
|
||||
increase_bytes_allocated(total_used);
|
||||
req.set_actual_size(words_size);
|
||||
// If !is_humongous, the "waste" is made availabe for new allocation
|
||||
if (waste_bytes > 0) {
|
||||
|
||||
@ -441,9 +441,6 @@ private:
|
||||
ShenandoahHeap* const _heap;
|
||||
ShenandoahRegionPartitions _partitions;
|
||||
|
||||
size_t _total_bytes_previously_allocated;
|
||||
size_t _mutator_bytes_at_last_sample;
|
||||
|
||||
// Temporarily holds mutator_Free allocatable bytes between prepare_to_rebuild() and finish_rebuild()
|
||||
size_t _prepare_to_rebuild_mutator_free;
|
||||
|
||||
@ -519,8 +516,6 @@ private:
|
||||
size_t _total_young_regions;
|
||||
size_t _total_global_regions;
|
||||
|
||||
size_t _mutator_bytes_allocated_since_gc_start;
|
||||
|
||||
// If only affiliation changes are promote-in-place and generation sizes have not changed,
|
||||
// we have AffiliatedChangesAreGlobalNeutral
|
||||
// If only affiliation changes are non-empty regions moved from Mutator to Collector and young size has not changed,
|
||||
@ -668,37 +663,6 @@ public:
|
||||
return _partitions.shrink_interval_if_range_modifies_either_boundary(partition, low_idx, high_idx, num_regions);
|
||||
}
|
||||
|
||||
void reset_bytes_allocated_since_gc_start(size_t initial_bytes_allocated);
|
||||
|
||||
void increase_bytes_allocated(size_t bytes);
|
||||
|
||||
// Return an approximation of the bytes allocated since GC start. The value returned is monotonically non-decreasing
|
||||
// in time within each GC cycle. For certain GC cycles, the value returned may include some bytes allocated before
|
||||
// the start of the current GC cycle.
|
||||
inline size_t get_bytes_allocated_since_gc_start() const {
|
||||
return _mutator_bytes_allocated_since_gc_start;
|
||||
}
|
||||
|
||||
inline size_t get_total_bytes_allocated() {
|
||||
return _mutator_bytes_allocated_since_gc_start + _total_bytes_previously_allocated;
|
||||
}
|
||||
|
||||
inline size_t get_bytes_allocated_since_previous_sample() {
|
||||
const size_t total_bytes_allocated = get_total_bytes_allocated();
|
||||
// total_bytes_allocated could overflow (wraps around) size_t in rare condition, we are relying on
|
||||
// wrap-around arithmetic of size_t type to produce meaningful result when total_bytes_allocated overflows
|
||||
// its 64-bit counter. The expression below is equivalent to code:
|
||||
// if (total_bytes < _mutator_bytes_at_last_sample) {
|
||||
// // overflow
|
||||
// return total_bytes + (SIZE_T_MAX - _mutator_bytes_at_last_sample) + 1;
|
||||
// } else {
|
||||
// return total_bytes - _mutator_bytes_at_last_sample;
|
||||
// }
|
||||
const size_t result = total_bytes_allocated - _mutator_bytes_at_last_sample;
|
||||
_mutator_bytes_at_last_sample = total_bytes_allocated;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Public because ShenandoahRegionPartitions assertions require access.
|
||||
inline size_t alloc_capacity(ShenandoahHeapRegion *r) const;
|
||||
inline size_t alloc_capacity(size_t idx) const;
|
||||
|
||||
@ -256,11 +256,6 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
|
||||
|
||||
GCIdMark gc_id_mark;
|
||||
|
||||
if ((gc_mode() != servicing_old) && (gc_mode() != stw_degenerated)) {
|
||||
// If mode is stw_degenerated, count bytes allocated from the start of the conc GC that experienced alloc failure.
|
||||
_heap->reset_bytes_allocated_since_gc_start();
|
||||
}
|
||||
|
||||
MetaspaceCombinedStats meta_sizes = MetaspaceUtils::get_combined_statistics();
|
||||
|
||||
// If GC was requested, we are sampling the counters even without actual triggers
|
||||
|
||||
@ -28,8 +28,9 @@
|
||||
#include "gc/shenandoah/shenandoahGlobalGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeap.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "gc/shenandoah/shenandoahVerifier.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
|
||||
|
||||
const char* ShenandoahGlobalGeneration::name() const {
|
||||
@ -49,10 +50,6 @@ size_t ShenandoahGlobalGeneration::used() const {
|
||||
return _free_set->global_used();
|
||||
}
|
||||
|
||||
size_t ShenandoahGlobalGeneration::bytes_allocated_since_gc_start() const {
|
||||
return _free_set->get_bytes_allocated_since_gc_start();
|
||||
}
|
||||
|
||||
size_t ShenandoahGlobalGeneration::get_affiliated_region_count() const {
|
||||
return _free_set->global_affiliated_regions();
|
||||
}
|
||||
@ -61,7 +58,6 @@ size_t ShenandoahGlobalGeneration::get_humongous_waste() const {
|
||||
return _free_set->total_humongous_waste();
|
||||
}
|
||||
|
||||
|
||||
size_t ShenandoahGlobalGeneration::used_regions() const {
|
||||
return _free_set->global_affiliated_regions();
|
||||
}
|
||||
|
||||
@ -26,8 +26,6 @@
|
||||
#define SHARE_VM_GC_SHENANDOAH_SHENANDOAHGLOBALGENERATION_HPP
|
||||
|
||||
#include "gc/shenandoah/shenandoahGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
|
||||
// A "generation" that represents the whole heap.
|
||||
class ShenandoahGlobalGeneration : public ShenandoahGeneration {
|
||||
@ -46,7 +44,6 @@ public:
|
||||
public:
|
||||
const char* name() const override;
|
||||
|
||||
size_t bytes_allocated_since_gc_start() const override;
|
||||
size_t used() const override;
|
||||
size_t used_regions() const override;
|
||||
size_t used_regions_size() const override;
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
#include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp"
|
||||
#include "gc/shenandoah/mode/shenandoahPassiveMode.hpp"
|
||||
#include "gc/shenandoah/mode/shenandoahSATBMode.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
|
||||
#include "gc/shenandoah/shenandoahBarrierSet.hpp"
|
||||
#include "gc/shenandoah/shenandoahClosures.inline.hpp"
|
||||
@ -822,8 +823,7 @@ bool ShenandoahHeap::check_soft_max_changed() {
|
||||
size_t new_soft_max = AtomicAccess::load(&SoftMaxHeapSize);
|
||||
size_t old_soft_max = soft_max_capacity();
|
||||
if (new_soft_max != old_soft_max) {
|
||||
new_soft_max = MAX2(min_capacity(), new_soft_max);
|
||||
new_soft_max = MIN2(max_capacity(), new_soft_max);
|
||||
new_soft_max = clamp(new_soft_max, min_capacity(), max_capacity());
|
||||
if (new_soft_max != old_soft_max) {
|
||||
log_info(gc)("Soft Max Heap Size: %zu%s -> %zu%s",
|
||||
byte_size_in_proper_unit(old_soft_max), proper_unit_for_byte_size(old_soft_max),
|
||||
@ -1033,35 +1033,41 @@ HeapWord* ShenandoahHeap::allocate_memory_under_lock(ShenandoahAllocRequest& req
|
||||
HeapWord* result = _free_set->allocate(req, in_new_region);
|
||||
|
||||
// Record the plab configuration for this result and register the object.
|
||||
if (result != nullptr && req.is_old()) {
|
||||
if (req.is_lab_alloc()) {
|
||||
old_generation()->configure_plab_for_current_thread(req);
|
||||
} else {
|
||||
// Register the newly allocated object while we're holding the global lock since there's no synchronization
|
||||
// built in to the implementation of register_object(). There are potential races when multiple independent
|
||||
// threads are allocating objects, some of which might span the same card region. For example, consider
|
||||
// a card table's memory region within which three objects are being allocated by three different threads:
|
||||
//
|
||||
// objects being "concurrently" allocated:
|
||||
// [-----a------][-----b-----][--------------c------------------]
|
||||
// [---- card table memory range --------------]
|
||||
//
|
||||
// Before any objects are allocated, this card's memory range holds no objects. Note that allocation of object a
|
||||
// wants to set the starts-object, first-start, and last-start attributes of the preceding card region.
|
||||
// Allocation of object b wants to set the starts-object, first-start, and last-start attributes of this card region.
|
||||
// Allocation of object c also wants to set the starts-object, first-start, and last-start attributes of this
|
||||
// card region.
|
||||
//
|
||||
// The thread allocating b and the thread allocating c can "race" in various ways, resulting in confusion, such as
|
||||
// last-start representing object b while first-start represents object c. This is why we need to require all
|
||||
// register_object() invocations to be "mutually exclusive" with respect to each card's memory range.
|
||||
old_generation()->card_scan()->register_object(result);
|
||||
if (result != nullptr) {
|
||||
if (req.is_mutator_alloc()) {
|
||||
_alloc_rate.allocated((req.actual_size() + req.waste()) * HeapWordSize);
|
||||
}
|
||||
|
||||
if (req.is_promotion()) {
|
||||
// Shared promotion.
|
||||
const size_t actual_size = req.actual_size() * HeapWordSize;
|
||||
log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
|
||||
old_generation()->expend_promoted(actual_size);
|
||||
if (req.is_old()) {
|
||||
if (req.is_lab_alloc()) {
|
||||
old_generation()->configure_plab_for_current_thread(req);
|
||||
} else {
|
||||
// Register the newly allocated object while we're holding the global lock since there's no synchronization
|
||||
// built in to the implementation of register_object(). There are potential races when multiple independent
|
||||
// threads are allocating objects, some of which might span the same card region. For example, consider
|
||||
// a card table's memory region within which three objects are being allocated by three different threads:
|
||||
//
|
||||
// objects being "concurrently" allocated:
|
||||
// [-----a------][-----b-----][--------------c------------------]
|
||||
// [---- card table memory range --------------]
|
||||
//
|
||||
// Before any objects are allocated, this card's memory range holds no objects. Note that allocation of object a
|
||||
// wants to set the starts-object, first-start, and last-start attributes of the preceding card region.
|
||||
// Allocation of object b wants to set the starts-object, first-start, and last-start attributes of this card region.
|
||||
// Allocation of object c also wants to set the starts-object, first-start, and last-start attributes of this
|
||||
// card region.
|
||||
//
|
||||
// The thread allocating b and the thread allocating c can "race" in various ways, resulting in confusion, such as
|
||||
// last-start representing object b while first-start represents object c. This is why we need to require all
|
||||
// register_object() invocations to be "mutually exclusive" with respect to each card's memory range.
|
||||
old_generation()->card_scan()->register_object(result);
|
||||
|
||||
if (req.is_promotion()) {
|
||||
// Shared promotion.
|
||||
const size_t actual_size = req.actual_size() * HeapWordSize;
|
||||
log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
|
||||
old_generation()->expend_promoted(actual_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2312,7 +2318,7 @@ void ShenandoahHeap::stop() {
|
||||
// Step 2. Wait until GC worker exits normally (this will cancel any ongoing GC).
|
||||
control_thread()->stop();
|
||||
|
||||
// Stop 4. Shutdown uncommit thread.
|
||||
// Step 3. Shutdown uncommit thread.
|
||||
if (_uncommit_thread != nullptr) {
|
||||
_uncommit_thread->stop();
|
||||
}
|
||||
@ -2423,27 +2429,6 @@ address ShenandoahHeap::in_cset_fast_test_addr() {
|
||||
return (address) heap->collection_set()->biased_map_address();
|
||||
}
|
||||
|
||||
void ShenandoahHeap::reset_bytes_allocated_since_gc_start() {
|
||||
// It is important to force_alloc_rate_sample() before the associated generation's bytes_allocated has been reset.
|
||||
// Note that we obtain heap lock to prevent additional allocations between sampling bytes_allocated_since_gc_start()
|
||||
// and reset_bytes_allocated_since_gc_start()
|
||||
{
|
||||
ShenandoahHeapLocker locker(lock());
|
||||
// unaccounted_bytes is the bytes not accounted for by our forced sample. If the sample interval is too short,
|
||||
// the "forced sample" will not happen, and any recently allocated bytes are "unaccounted for". We pretend these
|
||||
// bytes are allocated after the start of subsequent gc.
|
||||
size_t unaccounted_bytes;
|
||||
size_t bytes_allocated = _free_set->get_bytes_allocated_since_gc_start();
|
||||
if (mode()->is_generational()) {
|
||||
unaccounted_bytes = young_generation()->heuristics()->force_alloc_rate_sample(bytes_allocated);
|
||||
} else {
|
||||
// Single-gen Shenandoah uses global heuristics.
|
||||
unaccounted_bytes = heuristics()->force_alloc_rate_sample(bytes_allocated);
|
||||
}
|
||||
_free_set->reset_bytes_allocated_since_gc_start(unaccounted_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahHeap::set_degenerated_gc_in_progress(bool in_progress) {
|
||||
_degenerated_gc_in_progress.set_cond(in_progress);
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
#include "gc/shared/collectedHeap.hpp"
|
||||
#include "gc/shared/markBitMap.hpp"
|
||||
#include "gc/shenandoah/mode/shenandoahMode.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRate.hpp"
|
||||
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
|
||||
#include "gc/shenandoah/shenandoahAsserts.hpp"
|
||||
#include "gc/shenandoah/shenandoahController.hpp"
|
||||
@ -227,12 +228,12 @@ private:
|
||||
Atomic<size_t> _committed;
|
||||
shenandoah_padding(1);
|
||||
|
||||
ShenandoahAllocationRate _alloc_rate;
|
||||
|
||||
public:
|
||||
void increase_committed(size_t bytes);
|
||||
void decrease_committed(size_t bytes);
|
||||
|
||||
void reset_bytes_allocated_since_gc_start();
|
||||
|
||||
size_t min_capacity() const;
|
||||
size_t max_capacity() const override;
|
||||
size_t soft_max_capacity() const;
|
||||
@ -243,6 +244,10 @@ public:
|
||||
|
||||
void set_soft_max_capacity(size_t v);
|
||||
|
||||
ShenandoahAllocationRate& alloc_rate() {
|
||||
return _alloc_rate;
|
||||
}
|
||||
|
||||
// ---------- Periodic Tasks
|
||||
//
|
||||
public:
|
||||
|
||||
@ -796,11 +796,6 @@ size_t ShenandoahOldGeneration::used() const {
|
||||
return _free_set->old_used();
|
||||
}
|
||||
|
||||
size_t ShenandoahOldGeneration::bytes_allocated_since_gc_start() const {
|
||||
assert(ShenandoahHeap::heap()->mode()->is_generational(), "NON_GEN implies not generational");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t ShenandoahOldGeneration::get_affiliated_region_count() const {
|
||||
return _free_set->old_affiliated_regions();
|
||||
}
|
||||
|
||||
@ -353,7 +353,6 @@ public:
|
||||
|
||||
static const char* state_name(State state);
|
||||
|
||||
size_t bytes_allocated_since_gc_start() const override;
|
||||
size_t used() const override;
|
||||
size_t used_regions() const override;
|
||||
size_t used_regions_size() const override;
|
||||
|
||||
@ -26,15 +26,10 @@
|
||||
|
||||
#include "gc/shared/gcCause.hpp"
|
||||
#include "gc/shared/gcTrace.hpp"
|
||||
#include "gc/shared/gcWhen.hpp"
|
||||
#include "gc/shared/referenceProcessorStats.hpp"
|
||||
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
|
||||
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
|
||||
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
|
||||
#include "gc/shenandoah/shenandoahReferenceProcessor.hpp"
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
|
||||
#include "jfr/jfrEvents.hpp"
|
||||
#include "utilities/debug.hpp"
|
||||
|
||||
|
||||
@ -41,6 +41,9 @@
|
||||
#include "runtime/vmThread.hpp"
|
||||
#include "services/memoryService.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
class GCTimer;
|
||||
class ShenandoahGeneration;
|
||||
|
||||
@ -235,5 +238,20 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// Casting a double that cannot be represented as a size_t may result in undefined behavior.
|
||||
// This small function checks if the given double is representable in a size_t and returns
|
||||
// that representation if it is. Otherwise, if the double cannot be safely cast to a size_t
|
||||
// it returns zero.
|
||||
inline size_t shenandoah_safe_size_cast(const double d) {
|
||||
static constexpr double size_max_as_double = static_cast<double>(std::numeric_limits<size_t>::max());
|
||||
if (std::isnan(d) || d < 0 || d >= size_max_as_double) {
|
||||
// NaN is unordered, all comparisons will be false.
|
||||
// +Inf is always greater than, -Inf is always less than
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(d);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHUTILS_HPP
|
||||
|
||||
190
src/hotspot/share/gc/shenandoah/shenandoahWeightedSeq.cpp
Normal file
190
src/hotspot/share/gc/shenandoah/shenandoahWeightedSeq.cpp
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
ShenandoahWeightedSeq::ShenandoahWeightedSeq(uint size)
|
||||
: _size(size),
|
||||
_first_sample_index(0),
|
||||
_num_samples(0),
|
||||
_x_values(NEW_C_HEAP_ARRAY(double, _size, mtGC)),
|
||||
_y_values(NEW_C_HEAP_ARRAY(double, _size, mtGC)),
|
||||
_weights(NEW_C_HEAP_ARRAY(double, _size, mtGC)),
|
||||
_x_origin(0),
|
||||
_y_origin(0),
|
||||
_x_sum(0),
|
||||
_y_sum(0),
|
||||
_weighted_y_sum(0),
|
||||
_weighted_sum(0),
|
||||
_weighted_yy_sum(0),
|
||||
_xy_sum(0),
|
||||
_xx_sum(0),
|
||||
_yy_sum(0),
|
||||
_slope(0.0),
|
||||
_y_intercept(0.0),
|
||||
_residual_sd(0.0) {
|
||||
}
|
||||
|
||||
ShenandoahWeightedSeq::~ShenandoahWeightedSeq() {
|
||||
FREE_C_HEAP_ARRAY(_x_values);
|
||||
FREE_C_HEAP_ARRAY(_y_values);
|
||||
FREE_C_HEAP_ARRAY(_weights);
|
||||
}
|
||||
|
||||
void ShenandoahWeightedSeq::add(double x, double y) {
|
||||
if (_num_samples == 0) {
|
||||
add(x, y, 0.0);
|
||||
} else {
|
||||
const uint index = (_first_sample_index + _num_samples - 1) % _size;
|
||||
const double weight = x - _x_values[index];
|
||||
add(x, y, weight);
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahWeightedSeq::deduct_oldest_and_rebase(const double x_absolute, const double y_absolute, const double weight) {
|
||||
// Suppose we want to shift _x_origin by delta. Our accumulators for x
|
||||
// components are based on the relative value 'x - x_origin', call this 'a'.
|
||||
// We want to update our accumulators to hold 'a - delta'.
|
||||
// Our new value for
|
||||
// updated sum(x) = sum(a - delta)
|
||||
// = sum(a) - n * delta.
|
||||
// Similarly
|
||||
// updated sum(x^2) = sum((a - delta)^2)
|
||||
// = sum(a^2 - 2 * delta * a + delta^2)
|
||||
// = sum(a^2) - 2 * delta * sum(a) + n * delta^2
|
||||
// Finally
|
||||
// updated sum(xy) = sum(a - delta) * y
|
||||
// = sum(xy) - delta * sum(y)
|
||||
const double x_delta = x_absolute - _x_origin;
|
||||
const double y_delta = y_absolute - _y_origin;
|
||||
|
||||
// order matters here, we must use old _x_sum
|
||||
_xx_sum = _xx_sum - 2.0 * x_delta * _x_sum + _num_samples * x_delta * x_delta;
|
||||
_xy_sum = _xy_sum - x_delta * _y_sum;
|
||||
_x_sum = _x_sum - _num_samples * x_delta;
|
||||
_x_origin = x_absolute;
|
||||
|
||||
// similarly, rebase y
|
||||
_yy_sum = _yy_sum - 2.0 * y_delta * _y_sum + _num_samples * y_delta * y_delta;
|
||||
_xy_sum = _xy_sum - y_delta * _x_sum;
|
||||
_y_sum = _y_sum - _num_samples * y_delta;
|
||||
_y_origin = y_absolute;
|
||||
|
||||
// and our weighted sums
|
||||
_weighted_yy_sum = _weighted_yy_sum - 2.0 * y_delta * _weighted_y_sum + _weighted_sum * y_delta * y_delta;
|
||||
_weighted_y_sum = _weighted_y_sum - _weighted_sum * y_delta;
|
||||
_weighted_sum -= weight;
|
||||
}
|
||||
|
||||
void ShenandoahWeightedSeq::add_latest(double x_absolute, double y_absolute, double weight) {
|
||||
const double x_delta = x_absolute - _x_origin;
|
||||
const double y_delta = y_absolute - _y_origin;
|
||||
_x_sum += x_delta;
|
||||
_y_sum += y_delta;
|
||||
_xy_sum += x_delta * y_delta;
|
||||
_xx_sum += x_delta * x_delta;
|
||||
_yy_sum += y_delta * y_delta;
|
||||
_weighted_sum += weight;
|
||||
_weighted_y_sum += y_delta * weight;
|
||||
_weighted_yy_sum += y_delta * y_delta * weight;
|
||||
}
|
||||
|
||||
void ShenandoahWeightedSeq::add(double x, double y, double weight) {
|
||||
// Update best-fit linear regression
|
||||
const uint index = (_first_sample_index + _num_samples) % _size;
|
||||
if (_num_samples == _size) {
|
||||
deduct_oldest_and_rebase(_x_values[index], _y_values[index], _weights[index]);
|
||||
} else if (_num_samples == 0) {
|
||||
_x_origin = x;
|
||||
_y_origin = y;
|
||||
}
|
||||
|
||||
_x_values[index] = x;
|
||||
_y_values[index] = y;
|
||||
_weights[index] = weight;
|
||||
|
||||
add_latest(x, y, weight);
|
||||
|
||||
if (_num_samples < _size) {
|
||||
_num_samples++;
|
||||
} else {
|
||||
_first_sample_index = (_first_sample_index + 1) % _size;
|
||||
}
|
||||
|
||||
const double x_spread = _num_samples * _xx_sum - _x_sum * _x_sum;
|
||||
if (x_spread <= 0.0 || _num_samples < 2) {
|
||||
// All samples are the sample point, can't make a line
|
||||
_slope = 0;
|
||||
_y_intercept = y - _y_origin;
|
||||
_residual_sd = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
_slope = (_num_samples * _xy_sum - _x_sum * _y_sum) / x_spread;
|
||||
_y_intercept = (_y_sum - _slope * _x_sum) / _num_samples;
|
||||
const double total_sum_of_squares = _yy_sum - _y_sum * _y_sum / _num_samples;
|
||||
const double sum_of_cross_deviations = _xy_sum - _x_sum * _y_sum / _num_samples;
|
||||
const double residual_sum_of_squares = total_sum_of_squares - _slope * sum_of_cross_deviations;
|
||||
_residual_sd = std::sqrt(MAX2(residual_sum_of_squares, 0.0) / _num_samples);
|
||||
}
|
||||
|
||||
double ShenandoahWeightedSeq::predict(double x_absolute, double margin_of_error) const {
|
||||
const double prediction = predict_y(x_absolute) + _residual_sd * margin_of_error;
|
||||
if (prediction <= 0.0) {
|
||||
// return average time, rather than negative or zero time
|
||||
return average();
|
||||
}
|
||||
return prediction;
|
||||
}
|
||||
|
||||
double ShenandoahWeightedSeq::weighted_average() const {
|
||||
if (_weighted_sum <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return _weighted_y_sum / _weighted_sum + _y_origin;
|
||||
}
|
||||
|
||||
double ShenandoahWeightedSeq::weighted_sd() const {
|
||||
if (_weighted_sum <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
const double weighted_mean = _weighted_y_sum / _weighted_sum;
|
||||
const double variance = _weighted_yy_sum / _weighted_sum - weighted_mean * weighted_mean;
|
||||
return std::sqrt(MAX2(variance, 0.0));
|
||||
}
|
||||
|
||||
double ShenandoahWeightedSeq::sd() const {
|
||||
if (_num_samples < 2) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
const double mean = _y_sum / _num_samples;
|
||||
const double variance = _yy_sum / _num_samples - mean * mean;
|
||||
return std::sqrt(MAX2(variance, 0.0));
|
||||
}
|
||||
130
src/hotspot/share/gc/shenandoah/shenandoahWeightedSeq.hpp
Normal file
130
src/hotspot/share/gc/shenandoah/shenandoahWeightedSeq.hpp
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHWEIGHTEDSEQ_HPP
|
||||
#define SHARE_GC_SHENANDOAH_SHENANDOAHWEIGHTEDSEQ_HPP
|
||||
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
// Provides a weighted sequence of x, y pairs. Various statistical properties
|
||||
// such as weighted mean, standard deviation, the line of best fit and the
|
||||
// residual deviation (deviation about the line of best fit) are available.
|
||||
// These attributes are maintained incrementally as we expect this structure
|
||||
// to be read more often than it is written.
|
||||
class ShenandoahWeightedSeq {
|
||||
|
||||
uint _size;
|
||||
uint _first_sample_index;
|
||||
uint _num_samples;
|
||||
|
||||
double* const _x_values;
|
||||
double* const _y_values;
|
||||
double* const _weights;
|
||||
|
||||
// Values stored in the x,y accumulators will be reduced to avoid arithmetic
|
||||
// errors caused by loss of precision when working with large doubles. This
|
||||
// is particularly important for the common use case when x is a monotonically
|
||||
// increasing timestamp
|
||||
double _x_origin;
|
||||
double _y_origin;
|
||||
|
||||
double _x_sum;
|
||||
double _y_sum;
|
||||
double _weighted_y_sum;
|
||||
double _weighted_sum;
|
||||
double _weighted_yy_sum;
|
||||
double _xy_sum;
|
||||
double _xx_sum;
|
||||
double _yy_sum;
|
||||
|
||||
double _slope; // slope
|
||||
double _y_intercept; // y-intercept
|
||||
double _residual_sd; // sd on deviance from prediction
|
||||
|
||||
public:
|
||||
|
||||
explicit ShenandoahWeightedSeq(uint size);
|
||||
~ShenandoahWeightedSeq();
|
||||
|
||||
// Return last item x value added to the sequence (zero if sequence is empty).
|
||||
double last() const {
|
||||
if (_num_samples == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
const uint index = (_first_sample_index + _num_samples - 1) % _size;
|
||||
return _x_values[index];
|
||||
}
|
||||
|
||||
// Add x, y to the sequence. Weight will be calculated as x - last().
|
||||
void add(double x, double y);
|
||||
|
||||
|
||||
// Add x, y to the sequence using given weight.
|
||||
void add(double x, double y, double weight);
|
||||
|
||||
// Predict the next value in the sequence for a given x. Uses average
|
||||
// if the prediction is <= 0. This is a legacy method visible only for
|
||||
// testing.
|
||||
double predict(double x, double margin_of_error) const;
|
||||
|
||||
// The standard deviation of the samples about the line of best fit rather
|
||||
// than deviation about the mean.
|
||||
double residual_sd() const { return _residual_sd; }
|
||||
|
||||
// An unweighted mean.
|
||||
double average() const { return _y_sum / MAX2(_num_samples, 1u) + _y_origin; }
|
||||
|
||||
// The weighted mean for the sequence.
|
||||
double weighted_average() const;
|
||||
|
||||
// Standard deviation for the weighted mean.
|
||||
double weighted_sd() const;
|
||||
|
||||
// An unweighted standard deviation of the unweighted mean
|
||||
double sd() const;
|
||||
|
||||
// The slope for a line of best fit through the samples
|
||||
double slope() const { return _slope; }
|
||||
|
||||
// Predict the y-value for the given x value based on linear reg
|
||||
double predict_y(double x_absolute) const {
|
||||
return _slope * (x_absolute - _x_origin) + _y_intercept + _y_origin;
|
||||
}
|
||||
|
||||
// Provides the slope and y-intercept for the line of best fit through the sequence
|
||||
void fit_line(const double x_absolute, double& slope, double& intercept) const {
|
||||
slope = _slope;
|
||||
intercept = predict_y(x_absolute);
|
||||
}
|
||||
|
||||
private:
|
||||
// Removes about to be overwritten sample from x accumulators and rebases x origin
|
||||
void deduct_oldest_and_rebase(double x, double y, double weight);
|
||||
|
||||
// Record the sample into the sequence, update x, y accumulators
|
||||
void add_latest(double x, double y, double weight);
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHWEIGHTEDSEQ_HPP
|
||||
@ -100,11 +100,6 @@ size_t ShenandoahYoungGeneration::used() const {
|
||||
return _free_set->young_used();
|
||||
}
|
||||
|
||||
size_t ShenandoahYoungGeneration::bytes_allocated_since_gc_start() const {
|
||||
assert(ShenandoahHeap::heap()->mode()->is_generational(), "Young implies generational");
|
||||
return _free_set->get_bytes_allocated_since_gc_start();
|
||||
}
|
||||
|
||||
size_t ShenandoahYoungGeneration::get_affiliated_region_count() const {
|
||||
return _free_set->young_affiliated_regions();
|
||||
}
|
||||
|
||||
@ -69,11 +69,10 @@ public:
|
||||
|
||||
// Returns true if the young generation is configured to enqueue old
|
||||
// oops for the old generation mark queues.
|
||||
bool is_bootstrap_cycle() {
|
||||
bool is_bootstrap_cycle() const {
|
||||
return _old_gen_task_queues != nullptr;
|
||||
}
|
||||
|
||||
size_t bytes_allocated_since_gc_start() const override;
|
||||
size_t used() const override;
|
||||
size_t used_regions() const override;
|
||||
size_t used_regions_size() const override;
|
||||
|
||||
@ -34,22 +34,18 @@
|
||||
range, \
|
||||
constraint) \
|
||||
\
|
||||
product(uint, ShenandoahAccelerationSamplePeriod, 15, EXPERIMENTAL, \
|
||||
"When at least this much time (measured in ms) has passed " \
|
||||
"since the acceleration allocation rate was most recently " \
|
||||
"sampled, capture another allocation rate sample for the purpose "\
|
||||
"of detecting acceleration or momentary spikes in allocation " \
|
||||
"rate. A smaller value allows quicker response to changes in " \
|
||||
"allocation rates but is more vulnerable to noise and requires " \
|
||||
"more monitoring effort.") \
|
||||
product(uint, ShenandoahMomentaryAllocRateSampleWindow, 6, EXPERIMENTAL, \
|
||||
"The number of samples in the momentary allocation rate moving " \
|
||||
"average. This window serves to detect momentary spikes in the " \
|
||||
"allocation rate. A smaller value allows quicker response to " \
|
||||
"changes in the allocation rate but is more vulnerable to noise " \
|
||||
"and requires more monitoring effort. Must not be greater than " \
|
||||
"ShenandoahRecentAllocRateSampleWindow") \
|
||||
range(1, 1000) \
|
||||
\
|
||||
product(uint, ShenandoahRateAccelerationSampleSize, 8, EXPERIMENTAL, \
|
||||
"In selected ShenandoahControlIntervals " \
|
||||
"(if ShenandoahAccelerationSamplePeriod ms have passed " \
|
||||
"since previous allocation rate sample), " \
|
||||
"we compute the allocation rate since the previous rate was " \
|
||||
"sampled. This many samples are analyzed to determine whether " \
|
||||
product(uint, ShenandoahRecentAllocRateSampleWindow, 20, EXPERIMENTAL, \
|
||||
"The number of samples in the recent allocation rate moving " \
|
||||
"average. These samples are analyzed to determine whether " \
|
||||
"allocation rates are accelerating. Acceleration may occur " \
|
||||
"due to increasing client demand or due to phase changes in " \
|
||||
"an application. A larger value reduces sensitivity to " \
|
||||
@ -62,30 +58,13 @@
|
||||
"detected. If the last several of all samples are signficantly " \
|
||||
"larger than the other samples, the best fit line through all " \
|
||||
"sampled values will have an upward slope, manifesting as " \
|
||||
"acceleration.") \
|
||||
range(1,64) \
|
||||
"acceleration. Must not be greater than ShenandoahAllocRateSampleWindow") \
|
||||
range(1,5000) \
|
||||
\
|
||||
product(uint, ShenandoahMomentaryAllocationRateSpikeSampleSize, \
|
||||
2, EXPERIMENTAL, \
|
||||
"In selected ShenandoahControlIntervals " \
|
||||
"(if ShenandoahAccelerationSamplePeriod ms have passed " \
|
||||
"since previous allocation rate sample), we compute " \
|
||||
"the allocation rate since the previous rate was sampled. " \
|
||||
"The weighted average of this " \
|
||||
"many most recent momentary allocation rate samples is compared " \
|
||||
"against current allocation runway and anticipated GC time to " \
|
||||
"determine whether a spike in momentary allocation rate " \
|
||||
"justifies an early GC trigger. Momentary allocation spike " \
|
||||
"detection is in addition to previously implemented " \
|
||||
"ShenandoahAdaptiveInitialSpikeThreshold, the latter of which " \
|
||||
"is more effective at detecting slower spikes. The latter " \
|
||||
"spike detection samples at the rate specifieid by " \
|
||||
"ShenandoahAdaptiveSampleFrequencyHz. The value of this " \
|
||||
"parameter must be less than the value of " \
|
||||
"ShenandoahRateAccelerationSampleSize. A larger value makes " \
|
||||
"momentary spike detection less sensitive. A smaller value " \
|
||||
"may result in excessive GC triggers.") \
|
||||
range(1,64) \
|
||||
product(uint, ShenandoahAllocRateSampleWindow, 100, EXPERIMENTAL, \
|
||||
"The size of the moving window over which the average " \
|
||||
"baseline allocation rate is maintained.") \
|
||||
range(1,10000) \
|
||||
\
|
||||
product(uintx, ShenandoahGenerationalMinPIPUsage, 30, EXPERIMENTAL, \
|
||||
"(Generational mode only) What percent of a heap region " \
|
||||
@ -267,7 +246,7 @@
|
||||
range(0,100) \
|
||||
\
|
||||
product(uintx, ShenandoahAllocationThreshold, 0, EXPERIMENTAL, \
|
||||
"How many new allocations should happen since the last GC cycle " \
|
||||
"How many bytes may be allocated since the last GC cycle started "\
|
||||
"before some heuristics trigger the collection. In percents of " \
|
||||
"(soft) max heap size. Set to zero to effectively disable.") \
|
||||
range(0,100) \
|
||||
@ -291,34 +270,12 @@
|
||||
"to 100 effectively disables the shortcut.") \
|
||||
range(0,100) \
|
||||
\
|
||||
product(uintx, ShenandoahAdaptiveSampleFrequencyHz, 10, EXPERIMENTAL, \
|
||||
"The number of times per second to update the allocation rate " \
|
||||
"moving average.") \
|
||||
\
|
||||
product(uintx, ShenandoahAdaptiveSampleSizeSeconds, 10, EXPERIMENTAL, \
|
||||
"The size of the moving window over which the average " \
|
||||
"allocation rate is maintained. The total number of samples " \
|
||||
"is the product of this number and the sample frequency.") \
|
||||
\
|
||||
product(double, ShenandoahAdaptiveInitialConfidence, 1.8, EXPERIMENTAL, \
|
||||
"The number of standard deviations used to determine an initial " \
|
||||
"margin of error for the average cycle time and average " \
|
||||
"allocation rate. Increasing this value will cause the " \
|
||||
"heuristic to initiate more concurrent cycles." ) \
|
||||
\
|
||||
product(double, ShenandoahAdaptiveInitialSpikeThreshold, 1.8, EXPERIMENTAL, \
|
||||
"If the most recently sampled allocation rate is more than " \
|
||||
"this many standard deviations away from the moving average, " \
|
||||
"then a cycle is initiated. This value controls how sensitive " \
|
||||
"the heuristic is to allocation spikes. Decreasing this number " \
|
||||
"increases the sensitivity. ") \
|
||||
\
|
||||
product(double, ShenandoahAdaptiveDecayFactor, 0.5, EXPERIMENTAL, \
|
||||
"The decay factor (alpha) used for values in the weighted " \
|
||||
"moving average of cycle time and allocation rate. " \
|
||||
"Larger values give more weight to recent values.") \
|
||||
range(0,1.0) \
|
||||
\
|
||||
product(uintx, ShenandoahGuaranteedGCInterval, 5*60*1000, EXPERIMENTAL, \
|
||||
"Many heuristics would guarantee a concurrent GC cycle at " \
|
||||
"least with this interval. This is useful when large idle " \
|
||||
|
||||
@ -558,6 +558,14 @@ static SpecialFlag const special_jvm_flags[] = {
|
||||
{ "UseNewLongLShift", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{ "AggressiveHeap", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
|
||||
{"ShenandoahAccelerationSamplePeriod", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{"ShenandoahRateAccelerationSampleSize", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{"ShenandoahMomentaryAllocationRateSpikeSampleSize", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{"ShenandoahAdaptiveSampleFrequencyHz", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{"ShenandoahAdaptiveSampleSizeSeconds", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{"ShenandoahAdaptiveInitialSpikeThreshold",JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
{"ShenandoahAdaptiveDecayFactor", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
|
||||
|
||||
#ifdef ASSERT
|
||||
{ "DummyObsoleteTestFlag", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() },
|
||||
#endif
|
||||
|
||||
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "unittest.hpp"
|
||||
#include "gc/shared/gc_globals.hpp"
|
||||
|
||||
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
|
||||
|
||||
class ShenandoahMockClock {
|
||||
public:
|
||||
static volatile jlong Counter;
|
||||
static jlong elapsed_counter() {
|
||||
const jlong result = Counter;
|
||||
Counter += NANOSECS_PER_SEC;
|
||||
return result;
|
||||
}
|
||||
|
||||
static jlong elapsed_frequency() {
|
||||
return NANOSECS_PER_SEC;
|
||||
}
|
||||
};
|
||||
|
||||
volatile jlong ShenandoahMockClock::Counter = 0;
|
||||
|
||||
class ShenandoahAllocationRateTest : public testing::Test {
|
||||
protected:
|
||||
ShenandoahAllocationRateTest() {
|
||||
ShenandoahMockClock::Counter = 0;
|
||||
}
|
||||
|
||||
template<typename Clock>
|
||||
static void allocate(ShenandoahAllocRate<Clock>& rate, size_t quantity) {
|
||||
rate.allocated(quantity);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr uint BASELINE_SAMPLES = 100;
|
||||
constexpr uint RECENT_SAMPLES = 8;
|
||||
constexpr uint MOMENTARY_SAMPLES = 2;
|
||||
constexpr uint MINIMUM_SAMPLE_SIZE = 1024;
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, ignore_too_small_sample) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
rate.allocated(512);
|
||||
EXPECT_DOUBLE_EQ(rate.weighted_average(), 0);
|
||||
}
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, two_second_average) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
allocate(rate, 2048); // t = 1
|
||||
allocate(rate, 2048); // t = 2
|
||||
EXPECT_DOUBLE_EQ(rate.weighted_average(), 2048.0);
|
||||
}
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_small_number_of_samples) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
allocate(rate, 1024);
|
||||
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
|
||||
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(consumption.predicted_rate(), 0.0);
|
||||
EXPECT_EQ(consumption.accelerated_consumption(), 0UL);
|
||||
}
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_uniform_rate) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
for (uint i = 0; i < BASELINE_SAMPLES; ++i) {
|
||||
allocate(rate, 1024);
|
||||
}
|
||||
|
||||
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
|
||||
EXPECT_DOUBLE_EQ(rate.weighted_average(), 1024); // Average rate, 1024 bytes per tick
|
||||
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0); // No acceleration, rate is constant
|
||||
EXPECT_DOUBLE_EQ(consumption.momentary_rate(), 1024); // Momentary rate is the same as the average
|
||||
EXPECT_EQ(consumption.momentary_consumption(), 102400UL); // 100 clock ticks at 1024 bytes per tick
|
||||
EXPECT_EQ(consumption.accelerated_consumption(), 0UL);
|
||||
}
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_momentary_spike) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
for (uint i = 0; i < BASELINE_SAMPLES; ++i) {
|
||||
allocate(rate, 2048);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < RECENT_SAMPLES; ++i) {
|
||||
allocate(rate, 1024);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < MOMENTARY_SAMPLES + 1; ++i) {
|
||||
allocate(rate, 2048);
|
||||
}
|
||||
|
||||
// Here we simulate a situation where we are returning from a lull (avg 1024/s) back
|
||||
// to the baseline average allocation rate (2048/s). The momentary rate will reflect
|
||||
// the recent samples, but we will not consider this to be an acceleration.
|
||||
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
|
||||
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(consumption.momentary_rate(), 2048);
|
||||
EXPECT_EQ(consumption.momentary_consumption(), 204800UL);
|
||||
EXPECT_EQ(consumption.accelerated_consumption(), 0UL);
|
||||
}
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_accelerating) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(256, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
for (uint i = 0; i < BASELINE_SAMPLES; ++i) {
|
||||
allocate(rate, 512);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < RECENT_SAMPLES; ++i) {
|
||||
allocate(rate, 1024);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < MOMENTARY_SAMPLES + 1; ++i) {
|
||||
allocate(rate, 2048);
|
||||
}
|
||||
|
||||
// Setup as before, but pretend our baseline acceleration rate is lower (512). This
|
||||
// will evaluate the acceleration of the rate.
|
||||
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
|
||||
EXPECT_GE(consumption.acceleration(), 180.0);
|
||||
EXPECT_GE(consumption.predicted_rate(), 2047.0); // should be 2048, but can be 2047.9999 from fp issues
|
||||
EXPECT_GE(consumption.accelerated_consumption(), 102400UL);
|
||||
EXPECT_EQ(consumption.momentary_consumption(), 0UL);
|
||||
}
|
||||
|
||||
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_decelerating) {
|
||||
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
|
||||
for (uint i = 0; i < RECENT_SAMPLES; ++i) {
|
||||
allocate(rate, 2048);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < MOMENTARY_SAMPLES + 1; ++i) {
|
||||
allocate(rate, 1024);
|
||||
}
|
||||
|
||||
// In this setup, the allocation rate is declining.
|
||||
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
|
||||
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(consumption.momentary_rate(), 1024.0);
|
||||
EXPECT_EQ(consumption.momentary_consumption(), 102400UL);
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "unittest.hpp"
|
||||
|
||||
#include "gc/shenandoah/shenandoahCycleDuration.hpp"
|
||||
|
||||
TEST_VM(ShenandoahCycleDurationTest, empty_sanity) {
|
||||
ShenandoahCycleDuration cycles(5);
|
||||
EXPECT_DOUBLE_EQ(cycles.predict_duration(1.0, 1.0), 0.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahCycleDurationTest, predict_duration) {
|
||||
ShenandoahCycleDuration cycles(5);
|
||||
cycles.record_duration(1.0, 1.0);
|
||||
cycles.record_duration(2.0, 2.0);
|
||||
cycles.record_duration(3.0, 3.0);
|
||||
EXPECT_DOUBLE_EQ(cycles.predict_duration(4.0, 0.0), 4.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahCycleDurationTest, fallback_to_average) {
|
||||
ShenandoahCycleDuration cycles(5);
|
||||
cycles.record_duration(1.0, 5.0);
|
||||
cycles.record_duration(2.0, 4.0);
|
||||
cycles.record_duration(3.0, 3.0);
|
||||
// With this downward trend, predicted duration at 6 seconds would be zero, so
|
||||
// we fall back to the average (5 + 4 + 3 / 3 = 4)
|
||||
EXPECT_DOUBLE_EQ(cycles.predict_duration(6.0, 0.0), 4.0);
|
||||
// Average is 4.0, sd = sqrt((25+16+9)/3 - 16) = sqrt(50/3 - 16) = sqrt(2/3) ~ 0.816
|
||||
EXPECT_NEAR(cycles.predict_duration(6.0, 1.0), 4.0 + 0.816, 0.001);
|
||||
}
|
||||
155
test/hotspot/gtest/gc/shenandoah/test_shenandoahWeightedSeq.cpp
Normal file
155
test/hotspot/gtest/gc/shenandoah/test_shenandoahWeightedSeq.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
#include "unittest.hpp"
|
||||
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
|
||||
|
||||
constexpr uint SAMPLE_SIZE = 3;
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, empty_sanity) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
EXPECT_DOUBLE_EQ(seq.predict(0, 0), 0.0);
|
||||
EXPECT_DOUBLE_EQ(seq.predict(1, 0), 0.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, predict_flat_line) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 2);
|
||||
EXPECT_DOUBLE_EQ(seq.predict(1, 1), 2.0);
|
||||
EXPECT_DOUBLE_EQ(seq.predict(2, 1), 2.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 1);
|
||||
seq.add(2, 2);
|
||||
seq.add(3, 3);
|
||||
EXPECT_DOUBLE_EQ(seq.predict(4, 1), 4.0);
|
||||
EXPECT_DOUBLE_EQ(seq.predict(5, 1), 5.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x_squared) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 1);
|
||||
seq.add(2, 4);
|
||||
seq.add(3, 9);
|
||||
EXPECT_NEAR(seq.predict(4, 0), 12.666, 0.001);
|
||||
EXPECT_NEAR(seq.predict(5, 0), 16.666, 0.001);
|
||||
// Give a margin of error that incorporates residuals standard deviation
|
||||
EXPECT_NEAR(seq.predict(4, 1), 13.138, 0.001);
|
||||
EXPECT_NEAR(seq.predict(5, 1), 17.138, 0.001);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x_squared_overflow) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
for (uint i = 0; i < SAMPLE_SIZE; i++) {
|
||||
seq.add(1, 1);
|
||||
seq.add(2, 4);
|
||||
seq.add(3, 9);
|
||||
}
|
||||
|
||||
EXPECT_NEAR(seq.predict(4, 0), 12.666, 0.001);
|
||||
EXPECT_NEAR(seq.predict(5, 0), 16.666, 0.001);
|
||||
// Give a margin of error that incorporates residuals standard deviation
|
||||
EXPECT_NEAR(seq.predict(4, 1), 13.138, 0.001);
|
||||
EXPECT_NEAR(seq.predict(5, 1), 17.138, 0.001);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x_long_uptime) {
|
||||
// Simulates a JVM running ~66 days, using os::elapsedTime() as x.
|
||||
// With tight sampling (50ms intervals) this hits catastrophic cancellation
|
||||
// in x_spread = samples * _xx_sum − _x_sum_squared — both operands have magnitude
|
||||
// whose double-precision LSB is ~0.065, but the true spread is ~0.015.
|
||||
constexpr double OFFSET = 66.0 * 86400.0; // 66 days of uptime in seconds
|
||||
constexpr double DT = 0.05; // 50ms between samples
|
||||
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
uint i = 1;
|
||||
for (uint n = SAMPLE_SIZE * 2; i <= n; i++) {
|
||||
seq.add(OFFSET + i * DT, i);
|
||||
}
|
||||
|
||||
// Relationship: y = (x − OFFSET)/DT, so predict(OFFSET + i·DT) should give i
|
||||
EXPECT_NEAR(seq.predict(OFFSET + (i + 1) * DT, 0), i + 1, 0.001);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, identical_timestamps) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(100.0, 1.0);
|
||||
seq.add(100.0, 2.0);
|
||||
|
||||
// Should return a finite prediction (e.g. the average of y).
|
||||
EXPECT_TRUE(std::isfinite(seq.predict(101.0, 0)));
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, simple_average_no_samples) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
EXPECT_DOUBLE_EQ(seq.average(), 0.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, simple_average_one_sample) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 1);
|
||||
EXPECT_DOUBLE_EQ(seq.average(), 1.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, simple_average_overflow) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
for (uint i = 0; i < SAMPLE_SIZE + 1; i++) {
|
||||
seq.add(i, 1);
|
||||
}
|
||||
EXPECT_DOUBLE_EQ(seq.average(), 1.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, simple_average) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 1);
|
||||
seq.add(2, 1);
|
||||
EXPECT_DOUBLE_EQ(seq.average(), 1.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, simple_average_2) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 1);
|
||||
seq.add(2, 2);
|
||||
EXPECT_DOUBLE_EQ(seq.average(), 1.5);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, weighted_average_no_samples) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
EXPECT_DOUBLE_EQ(seq.weighted_average(), 0.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, weighted_average_one_sample) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
seq.add(1, 2, 2);
|
||||
EXPECT_DOUBLE_EQ(seq.weighted_average(), 2.0);
|
||||
}
|
||||
|
||||
TEST_VM(ShenandoahWeightedSeqTest, weighted_average_overflow) {
|
||||
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
|
||||
for (uint i = 0; i < SAMPLE_SIZE + 1; i++) {
|
||||
seq.add(i, 2, 2);
|
||||
}
|
||||
EXPECT_DOUBLE_EQ(seq.weighted_average(), 2.0);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user