Bug 1570905 - Rework the GC triggers to make the incremental trigger the default and the non-incremental trigger some factor of this r=sfink?

Differential Revision: https://phabricator.services.mozilla.com/D41606

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jon Coppeard 2019-08-13 08:40:36 +00:00
Родитель dea1e66fa8
Коммит 992d6b6ce5
8 изменённых файлов: 128 добавлений и 125 удалений

Просмотреть файл

@ -2572,12 +2572,12 @@ void nsJSContext::EnsureStatics() {
(void*)JSGC_ALLOCATION_THRESHOLD);
Preferences::RegisterCallbackAndCall(
SetMemoryPrefChangedCallbackInt,
"javascript.options.mem.gc_allocation_threshold_factor",
(void*)JSGC_ALLOCATION_THRESHOLD_FACTOR);
"javascript.options.mem.gc_non_incremental_factor",
(void*)JSGC_NON_INCREMENTAL_FACTOR);
Preferences::RegisterCallbackAndCall(
SetMemoryPrefChangedCallbackInt,
"javascript.options.mem.gc_allocation_threshold_factor_avoid_interrupt",
(void*)JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT);
"javascript.options.mem.gc_avoid_interrupt_factor",
(void*)JSGC_AVOID_INTERRUPT_FACTOR);
Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
"dom.cycle_collector.incremental");

Просмотреть файл

@ -130,7 +130,7 @@ typedef enum JSGCParamKey {
* The "do we collect?" decision depends on various parameters and can be
* summarised as:
*
* ZoneSize * 1/UsageFactor > Max(ThresholdBase, LastSize) * GrowthFactor
* ZoneSize > Max(ThresholdBase, LastSize) * GrowthFactor * ThresholdFactor
*
* Where
* ZoneSize: Current size of this zone.
@ -139,9 +139,10 @@ typedef enum JSGCParamKey {
* GrowthFactor: A number above 1, calculated based on some of the
* following parameters.
* See computeZoneHeapGrowthFactorForHeapSize() in GC.cpp
* UsageFactor: JSGC_ALLOCATION_THRESHOLD_FACTOR or
* JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT or 1.0 for
* non-incremental collections.
* ThresholdFactor: 1.0 for incremental collections or
* JSGC_NON_INCREMENTAL_FACTOR or
* JSGC_AVOID_INTERRUPT_FACTOR for non-incremental
* collections.
*
* The RHS of the equation above is calculated and sets
* zone->threshold.gcTriggerBytes(). When usage.gcBytes() surpasses
@ -251,26 +252,26 @@ typedef enum JSGCParamKey {
JSGC_COMPACTING_ENABLED = 23,
/**
* Percentage for triggering a GC based on zone->threshold.gcTriggerBytes().
* Percentage for how far over a trigger threshold we go before triggering a
* non-incremental GC.
*
* When the heap reaches this percentage of the allocation threshold an
* incremental collection is started.
* We trigger an incremental GC when a trigger threshold is reached but the
* collection may not be fast enough to keep up with the mutator. At some
* point we finish the collection non-incrementally.
*
* Default: ZoneAllocThresholdFactorDefault
* Default: NonIncrementalFactor
* Pref: None
*/
JSGC_ALLOCATION_THRESHOLD_FACTOR = 25,
JSGC_NON_INCREMENTAL_FACTOR = 25,
/**
* Percentage for triggering a GC based on zone->threshold.gcTriggerBytes().
* Percentage for how far over a trigger threshold we go before triggering an
* incremental collection that would reset an in-progress collection.
*
* Used instead of the above percentage if if another GC (in different zones)
* is already running.
*
* Default: ZoneAllocThresholdFactorAvoidInterruptDefault
* Default: AvoidInterruptFactor
* Pref: None
*/
JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT = 26,
JSGC_AVOID_INTERRUPT_FACTOR = 26,
/**
* Attempt to run a minor GC in the idle time if the free space falls

Просмотреть файл

@ -513,9 +513,8 @@ static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) {
_("dynamicHeapGrowth", JSGC_DYNAMIC_HEAP_GROWTH, true) \
_("dynamicMarkSlice", JSGC_DYNAMIC_MARK_SLICE, true) \
_("allocationThreshold", JSGC_ALLOCATION_THRESHOLD, true) \
_("allocationThresholdFactor", JSGC_ALLOCATION_THRESHOLD_FACTOR, true) \
_("allocationThresholdFactorAvoidInterrupt", \
JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT, true) \
_("nonIncrementalFactor", JSGC_NON_INCREMENTAL_FACTOR, true) \
_("avoidInterruptFactor", JSGC_AVOID_INTERRUPT_FACTOR, true) \
_("minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT, true) \
_("maxEmptyChunkCount", JSGC_MAX_EMPTY_CHUNK_COUNT, true) \
_("compactingEnabled", JSGC_COMPACTING_ENABLED, true) \

Просмотреть файл

@ -283,7 +283,7 @@ namespace gc {
namespace TuningDefaults {
/* JSGC_ALLOCATION_THRESHOLD */
static const size_t GCZoneAllocThresholdBase = 30 * 1024 * 1024;
static const size_t GCZoneAllocThresholdBase = 27 * 1024 * 1024;
/*
* JSGC_MIN_NURSERY_BYTES
@ -294,11 +294,11 @@ static const size_t GCZoneAllocThresholdBase = 30 * 1024 * 1024;
*/
static const size_t GCMinNurseryBytes = 256 * 1024;
/* JSGC_ALLOCATION_THRESHOLD_FACTOR */
static const float AllocThresholdFactor = 0.9f;
/* JSGC_NON_INCREMENTAL_FACTOR */
static const float NonIncrementalFactor = 1.12f;
/* JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT */
static const float AllocThresholdFactorAvoidInterrupt = 0.9f;
/* JSGC_AVOID_INTERRUPT_FACTOR */
static const float AvoidInterruptFactor = 1.0f;
/* JSGC_ZONE_ALLOC_DELAY_KB */
static const size_t ZoneAllocDelayBytes = 1024 * 1024;
@ -359,7 +359,7 @@ static const float PretenureGroupThreshold = 3000;
static const auto MinLastDitchGCPeriod = 60; // in seconds
/* JSGC_MALLOC_THRESHOLD_BASE */
static const size_t MallocThresholdBase = 42 * 1024 * 1024;
static const size_t MallocThresholdBase = 38 * 1024 * 1024;
/* JSGC_MALLOC_GROWTH_FACTOR */
static const float MallocGrowthFactor = 1.5f;
@ -368,14 +368,6 @@ static const float MallocGrowthFactor = 1.5f;
} // namespace gc
} // namespace js
/*
* We start to incremental collection for a zone when a proportion of its
* threshold is reached. This is configured by the
* JSGC_ALLOCATION_THRESHOLD_FACTOR and
* JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT parameters.
*/
static constexpr float MinAllocationThresholdFactor = 0.9f;
/*
* We may start to collect a zone before its trigger threshold is reached if
* GCRuntime::maybeGC() is called for that zone or we start collecting other
@ -385,13 +377,12 @@ static constexpr float HighFrequencyEagerAllocTriggerFactor = 0.85f;
static constexpr float LowFrequencyEagerAllocTriggerFactor = 0.9f;
/*
* Don't allow heap growth factors to be set so low that collections could
* Don't allow heap growth factors to be set so low that eager collections could
* reduce the trigger threshold.
*/
static constexpr float MinHeapGrowthFactor =
1.0f /
Min(HighFrequencyEagerAllocTriggerFactor,
Min(LowFrequencyEagerAllocTriggerFactor, MinAllocationThresholdFactor));
1.0f / Min(HighFrequencyEagerAllocTriggerFactor,
LowFrequencyEagerAllocTriggerFactor);
/* Increase the IGC marking slice time if we are in highFrequencyGC mode. */
static constexpr int IGC_MARK_SLICE_MULTIPLIER = 2;
@ -1535,20 +1526,20 @@ bool GCSchedulingTunables::setParameter(JSGCParamKey key, uint32_t value,
case JSGC_ALLOCATION_THRESHOLD:
gcZoneAllocThresholdBase_ = value * 1024 * 1024;
break;
case JSGC_ALLOCATION_THRESHOLD_FACTOR: {
case JSGC_NON_INCREMENTAL_FACTOR: {
float newFactor = value / 100.0f;
if (newFactor < MinAllocationThresholdFactor || newFactor > 1.0f) {
if (newFactor < 1.0f) {
return false;
}
allocThresholdFactor_ = newFactor;
nonIncrementalFactor_ = newFactor;
break;
}
case JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT: {
case JSGC_AVOID_INTERRUPT_FACTOR: {
float newFactor = value / 100.0f;
if (newFactor < MinAllocationThresholdFactor || newFactor > 1.0f) {
if (newFactor < 1.0f) {
return false;
}
allocThresholdFactorAvoidInterrupt_ = newFactor;
avoidInterruptFactor_ = newFactor;
break;
}
case JSGC_MIN_EMPTY_CHUNK_COUNT:
@ -1667,9 +1658,8 @@ GCSchedulingTunables::GCSchedulingTunables()
gcMinNurseryBytes_(TuningDefaults::GCMinNurseryBytes),
gcMaxNurseryBytes_(0),
gcZoneAllocThresholdBase_(TuningDefaults::GCZoneAllocThresholdBase),
allocThresholdFactor_(TuningDefaults::AllocThresholdFactor),
allocThresholdFactorAvoidInterrupt_(
TuningDefaults::AllocThresholdFactorAvoidInterrupt),
nonIncrementalFactor_(TuningDefaults::NonIncrementalFactor),
avoidInterruptFactor_(TuningDefaults::AvoidInterruptFactor),
zoneAllocDelayBytes_(TuningDefaults::ZoneAllocDelayBytes),
dynamicHeapGrowthEnabled_(TuningDefaults::DynamicHeapGrowthEnabled),
highFrequencyThreshold_(
@ -1757,12 +1747,11 @@ void GCSchedulingTunables::resetParameter(JSGCParamKey key,
case JSGC_ALLOCATION_THRESHOLD:
gcZoneAllocThresholdBase_ = TuningDefaults::GCZoneAllocThresholdBase;
break;
case JSGC_ALLOCATION_THRESHOLD_FACTOR:
allocThresholdFactor_ = TuningDefaults::AllocThresholdFactor;
case JSGC_NON_INCREMENTAL_FACTOR:
nonIncrementalFactor_ = TuningDefaults::NonIncrementalFactor;
break;
case JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT:
allocThresholdFactorAvoidInterrupt_ =
TuningDefaults::AllocThresholdFactorAvoidInterrupt;
case JSGC_AVOID_INTERRUPT_FACTOR:
avoidInterruptFactor_ = TuningDefaults::AvoidInterruptFactor;
break;
case JSGC_MIN_EMPTY_CHUNK_COUNT:
setMinEmptyChunkCount(TuningDefaults::MinEmptyChunkCount);
@ -1850,10 +1839,10 @@ uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) {
return tunables.isDynamicMarkSliceEnabled();
case JSGC_ALLOCATION_THRESHOLD:
return tunables.gcZoneAllocThresholdBase() / 1024 / 1024;
case JSGC_ALLOCATION_THRESHOLD_FACTOR:
return uint32_t(tunables.allocThresholdFactor() * 100);
case JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT:
return uint32_t(tunables.allocThresholdFactorAvoidInterrupt() * 100);
case JSGC_NON_INCREMENTAL_FACTOR:
return uint32_t(tunables.nonIncrementalFactor() * 100);
case JSGC_AVOID_INTERRUPT_FACTOR:
return uint32_t(tunables.avoidInterruptFactor() * 100);
case JSGC_MIN_EMPTY_CHUNK_COUNT:
return tunables.minEmptyChunkCount(lock);
case JSGC_MAX_EMPTY_CHUNK_COUNT:
@ -2130,12 +2119,14 @@ float ZoneHeapThreshold::computeZoneHeapGrowthFactorForHeapSize(
size_t ZoneHeapThreshold::computeZoneTriggerBytes(
float growthFactor, size_t lastBytes, JSGCInvocationKind gckind,
const GCSchedulingTunables& tunables, const AutoLockGC& lock) {
size_t base =
gckind == GC_SHRINK
? Max(lastBytes, tunables.minEmptyChunkCount(lock) * ChunkSize)
: Max(lastBytes, tunables.gcZoneAllocThresholdBase());
size_t baseMin = gckind == GC_SHRINK
? tunables.minEmptyChunkCount(lock) * ChunkSize
: tunables.gcZoneAllocThresholdBase();
size_t base = Max(lastBytes, baseMin);
float trigger = float(base) * growthFactor;
return size_t(Min(float(tunables.gcMaxBytes()), trigger));
float triggerMax =
float(tunables.gcMaxBytes()) / tunables.nonIncrementalFactor();
return size_t(Min(triggerMax, trigger));
}
void ZoneHeapThreshold::updateAfterGC(size_t lastBytes,
@ -3425,43 +3416,45 @@ void GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, size_t nbytes) {
size_t usedBytes =
zone->zoneSize.gcBytes(); // This already includes |nbytes|.
size_t thresholdBytes = zone->threshold.gcTriggerBytes();
if (usedBytes >= thresholdBytes) {
// The threshold has been surpassed, immediately trigger a GC, which
// will be done non-incrementally.
triggerZoneGC(zone, JS::GCReason::ALLOC_TRIGGER, usedBytes, thresholdBytes);
if (usedBytes < thresholdBytes) {
return;
}
bool wouldInterruptCollection =
isIncrementalGCInProgress() && !zone->isCollecting();
float zoneGCThresholdFactor =
wouldInterruptCollection ? tunables.allocThresholdFactorAvoidInterrupt()
: tunables.allocThresholdFactor();
size_t niThreshold = thresholdBytes * tunables.nonIncrementalFactor();
if (usedBytes >= niThreshold) {
// We have passed the non-incremental threshold: immediately trigger a
// non-incremental GC.
triggerZoneGC(zone, JS::GCReason::ALLOC_TRIGGER, usedBytes, niThreshold);
return;
}
size_t igcThresholdBytes = thresholdBytes * zoneGCThresholdFactor;
// Use a higher threshold if starting a GC would reset an in-progress
// collection.
if (isIncrementalGCInProgress() && !zone->isCollecting() &&
usedBytes < thresholdBytes * tunables.avoidInterruptFactor()) {
return;
}
if (usedBytes >= igcThresholdBytes) {
// During an incremental GC, reduce the delay to the start of the next
// incremental slice.
if (zone->gcDelayBytes < nbytes) {
zone->gcDelayBytes = 0;
} else {
zone->gcDelayBytes -= nbytes;
}
// During an incremental GC, reduce the delay to the start of the next
// incremental slice.
if (zone->gcDelayBytes < nbytes) {
zone->gcDelayBytes = 0;
} else {
zone->gcDelayBytes -= nbytes;
}
if (!zone->gcDelayBytes) {
// Start or continue an in progress incremental GC. We do this
// to try to avoid performing non-incremental GCs on zones
// which allocate a lot of data, even when incremental slices
// can't be triggered via scheduling in the event loop.
triggerZoneGC(zone, JS::GCReason::INCREMENTAL_ALLOC_TRIGGER, usedBytes,
igcThresholdBytes);
if (!zone->gcDelayBytes) {
// Start or continue an in progress incremental GC. We do this
// to try to avoid performing non-incremental GCs on zones
// which allocate a lot of data, even when incremental slices
// can't be triggered via scheduling in the event loop.
triggerZoneGC(zone, JS::GCReason::INCREMENTAL_ALLOC_TRIGGER, usedBytes,
thresholdBytes);
// Delay the next slice until a certain amount of allocation
// has been performed.
zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
return;
}
// Delay the next slice until a certain amount of allocation
// has been performed.
zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
return;
}
}
@ -3497,22 +3490,28 @@ bool GCRuntime::maybeMallocTriggerZoneGC(Zone* zone, const HeapSize& heap,
size_t usedBytes = heap.gcBytes();
size_t thresholdBytes = threshold.gcTriggerBytes();
if (usedBytes >= thresholdBytes) {
// The threshold has been surpassed, immediately trigger a GC, which
// will be done non-incrementally.
triggerZoneGC(zone, reason, usedBytes, thresholdBytes);
if (usedBytes < thresholdBytes) {
return false;
}
size_t niThreshold = thresholdBytes * tunables.nonIncrementalFactor();
if (usedBytes >= thresholdBytes * niThreshold) {
// We have passed the non-incremental threshold: immediately trigger a
// non-incremental GC.
triggerZoneGC(zone, reason, usedBytes, niThreshold);
return true;
}
float zoneGCThresholdFactor = tunables.allocThresholdFactor();
size_t igcThresholdBytes = thresholdBytes * zoneGCThresholdFactor;
if (usedBytes >= igcThresholdBytes) {
// Start or continue an in progress incremental GC.
triggerZoneGC(zone, reason, usedBytes, igcThresholdBytes);
return true;
// Use a higher threshold if starting a GC would reset an in-progress
// collection.
if (isIncrementalGCInProgress() && !zone->isCollecting() &&
usedBytes < thresholdBytes * tunables.avoidInterruptFactor()) {
return false;
}
return false;
// Start or continue an in progress incremental GC.
triggerZoneGC(zone, reason, usedBytes, thresholdBytes);
return true;
}
bool GCRuntime::triggerZoneGC(Zone* zone, JS::GCReason reason, size_t used,
@ -7422,7 +7421,8 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC(
continue;
}
if (zone->zoneSize.gcBytes() >= zone->threshold.gcTriggerBytes()) {
if (zone->zoneSize.gcBytes() >=
zone->threshold.nonIncrementalTriggerBytes(tunables)) {
CheckZoneIsScheduled(zone, reason, "GC bytes");
budget.makeUnlimited();
stats().nonincremental(AbortReason::GCBytesTrigger);
@ -7432,7 +7432,7 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC(
}
if (zone->gcMallocBytes.gcBytes() >=
zone->gcMallocThreshold.gcTriggerBytes()) {
zone->gcMallocThreshold.nonIncrementalTriggerBytes(tunables)) {
CheckZoneIsScheduled(zone, reason, "malloc bytes");
budget.makeUnlimited();
stats().nonincremental(AbortReason::MallocBytesTrigger);
@ -7441,7 +7441,8 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC(
}
}
if (zone->gcJitBytes.gcBytes() >= zone->gcJitThreshold.gcTriggerBytes()) {
if (zone->gcJitBytes.gcBytes() >=
zone->gcJitThreshold.nonIncrementalTriggerBytes(tunables)) {
CheckZoneIsScheduled(zone, reason, "JIT code bytes");
budget.makeUnlimited();
stats().nonincremental(AbortReason::JitCodeBytesTrigger);

Просмотреть файл

@ -355,18 +355,19 @@ class GCSchedulingTunables {
MainThreadOrGCTaskData<size_t> gcZoneAllocThresholdBase_;
/*
* JSGC_ALLOCATION_THRESHOLD_FACTOR
* JSGC_NON_INCREMENTAL_FACTOR
*
* Fraction of threshold.gcBytes() which triggers an incremental GC.
* Multiple of threshold.gcBytes() which triggers a non-incremental GC.
*/
UnprotectedData<float> allocThresholdFactor_;
UnprotectedData<float> nonIncrementalFactor_;
/*
* JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT
* JSGC_AVOID_INTERRUPT_FACTOR
*
* The same except when doing so would interrupt an already running GC.
* Multiple of threshold.gcBytes() which triggers a new incremental GC when
* doing so would interrupt an ongoing incremental GC.
*/
UnprotectedData<float> allocThresholdFactorAvoidInterrupt_;
UnprotectedData<float> avoidInterruptFactor_;
/*
* Number of bytes to allocate between incremental slices in GCs triggered
@ -488,10 +489,8 @@ class GCSchedulingTunables {
size_t gcMinNurseryBytes() const { return gcMinNurseryBytes_; }
size_t gcMaxNurseryBytes() const { return gcMaxNurseryBytes_; }
size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; }
double allocThresholdFactor() const { return allocThresholdFactor_; }
double allocThresholdFactorAvoidInterrupt() const {
return allocThresholdFactorAvoidInterrupt_;
}
double nonIncrementalFactor() const { return nonIncrementalFactor_; }
double avoidInterruptFactor() const { return avoidInterruptFactor_; }
size_t zoneAllocDelayBytes() const { return zoneAllocDelayBytes_; }
bool isDynamicHeapGrowthEnabled() const { return dynamicHeapGrowthEnabled_; }
const mozilla::TimeDuration& highFrequencyThreshold() const {
@ -656,6 +655,9 @@ class ZoneThreshold {
public:
size_t gcTriggerBytes() const { return gcTriggerBytes_; }
size_t nonIncrementalTriggerBytes(GCSchedulingTunables& tunables) const {
return gcTriggerBytes_ * tunables.nonIncrementalFactor();
}
float eagerAllocTrigger(bool highFrequencyGC) const;
};

Просмотреть файл

@ -147,9 +147,7 @@ class ZoneAllocator : public JS::shadow::Zone,
const js::gc::ZoneThreshold& threshold,
JS::GCReason reason) {
JSRuntime* rt = runtimeFromAnyThread();
float factor = rt->gc.tunables.allocThresholdFactor();
size_t thresholdBytes = threshold.gcTriggerBytes() * factor;
if (heap.gcBytes() >= thresholdBytes &&
if (heap.gcBytes() >= threshold.gcTriggerBytes() &&
rt->heapState() == JS::HeapState::Idle) {
gc::MaybeMallocTriggerZoneGC(rt, this, heap, threshold, reason);
}

Просмотреть файл

@ -47,6 +47,8 @@ testChangeParam("lowFrequencyHeapGrowth");
testChangeParam("dynamicHeapGrowth");
testChangeParam("dynamicMarkSlice");
testChangeParam("allocationThreshold");
testChangeParam("nonIncrementalFactor");
testChangeParam("avoidInterruptFactor");
testChangeParam("minEmptyChunkCount");
testChangeParam("maxEmptyChunkCount");
testChangeParam("compactingEnabled");

Просмотреть файл

@ -1424,13 +1424,13 @@ pref("javascript.options.mem.gc_dynamic_heap_growth", true);
pref("javascript.options.mem.gc_dynamic_mark_slice", true);
// JSGC_ALLOCATION_THRESHOLD
pref("javascript.options.mem.gc_allocation_threshold_mb", 30);
pref("javascript.options.mem.gc_allocation_threshold_mb", 27);
// JSGC_ALLOCATION_THRESHOLD_FACTOR
pref("javascript.options.mem.gc_allocation_threshold_factor", 90);
// JSGC_NON_INCREMENTAL_FACTOR
pref("javascript.options.mem.gc_non_incremental_factor", 112);
// JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT
pref("javascript.options.mem.gc_allocation_threshold_factor_avoid_interrupt", 90);
// JSGC_AVOID_INTERRUPT_FACTOR
pref("javascript.options.mem.gc_avoid_interrupt_factor", 100);
// JSGC_MIN_EMPTY_CHUNK_COUNT
pref("javascript.options.mem.gc_min_empty_chunk_count", 1);