diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 1dc1cfa3c329..1ba2c302b1ec 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2607,6 +2607,16 @@ void nsJSContext::EnsureStatics() { "javascript.options.mem.gc_high_frequency_time_limit_ms", (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT); + Preferences::RegisterCallbackAndCall( + SetMemoryPrefChangedCallbackBool, + "javascript.options.mem.gc_dynamic_mark_slice", + (void*)JSGC_DYNAMIC_MARK_SLICE); + + Preferences::RegisterCallbackAndCall( + SetMemoryPrefChangedCallbackBool, + "javascript.options.mem.gc_dynamic_heap_growth", + (void*)JSGC_DYNAMIC_HEAP_GROWTH); + Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_low_frequency_heap_growth", diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 82dd832701bb..7377f6a6bddb 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -489,6 +489,24 @@ void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) { continue; } + matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_heap_growth"); + if (memPrefName == matchName || + (gRuntimeServiceDuringInit && index == 10)) { + bool prefValue = GetWorkerPref(matchName, false); + UpdateOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_HEAP_GROWTH, + prefValue ? 0 : 1); + continue; + } + + matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_mark_slice"); + if (memPrefName == matchName || + (gRuntimeServiceDuringInit && index == 11)) { + bool prefValue = GetWorkerPref(matchName, false); + UpdateOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_MARK_SLICE, + prefValue ? 0 : 1); + continue; + } + matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_min_empty_chunk_count"); if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 12)) { diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index ff7f955bbb54..9c15ccb86b25 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -188,6 +188,23 @@ typedef enum JSGCParamKey { */ JSGC_LOW_FREQUENCY_HEAP_GROWTH = 16, + /** + * If false, the heap growth factor is fixed at 3. If true, it is determined + * based on whether GCs are high- or low- frequency. + * + * Pref: javascript.options.mem.gc_dynamic_heap_growth + * Default: DynamicHeapGrowthEnabled + */ + JSGC_DYNAMIC_HEAP_GROWTH = 17, + + /** + * If true, high-frequency GCs will use a longer mark slice. + * + * Pref: javascript.options.mem.gc_dynamic_mark_slice + * Default: DynamicMarkSliceEnabled + */ + JSGC_DYNAMIC_MARK_SLICE = 18, + /** * Lower limit for collecting a zone. * diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index a28769be23b7..17a653ef5dd0 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -566,6 +566,8 @@ static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) { _("highFrequencyHeapGrowthMax", JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX, true) \ _("highFrequencyHeapGrowthMin", JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN, true) \ _("lowFrequencyHeapGrowth", JSGC_LOW_FREQUENCY_HEAP_GROWTH, true) \ + _("dynamicHeapGrowth", JSGC_DYNAMIC_HEAP_GROWTH, true) \ + _("dynamicMarkSlice", JSGC_DYNAMIC_MARK_SLICE, true) \ _("allocationThreshold", JSGC_ALLOCATION_THRESHOLD, true) \ _("nonIncrementalFactor", JSGC_NON_INCREMENTAL_FACTOR, true) \ _("minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT, true) \ diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index 3624a46f2676..4c2f96afb7c3 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -1468,6 +1468,10 @@ uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) { return uint32_t(tunables.highFrequencyHeapGrowthMin() * 100); case JSGC_LOW_FREQUENCY_HEAP_GROWTH: return uint32_t(tunables.lowFrequencyHeapGrowth() * 100); + case JSGC_DYNAMIC_HEAP_GROWTH: + return tunables.isDynamicHeapGrowthEnabled(); + case JSGC_DYNAMIC_MARK_SLICE: + return tunables.isDynamicMarkSliceEnabled(); case JSGC_ALLOCATION_THRESHOLD: return tunables.gcZoneAllocThresholdBase() / 1024 / 1024; case JSGC_NON_INCREMENTAL_FACTOR: @@ -7296,7 +7300,8 @@ SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) { if (millis == 0) { if (reason == JS::GCReason::ALLOC_TRIGGER) { millis = defaultSliceBudgetMS(); - } else if (schedulingState.inHighFrequencyGCMode()) { + } else if (schedulingState.inHighFrequencyGCMode() && + tunables.isDynamicMarkSliceEnabled()) { millis = defaultSliceBudgetMS() * IGC_MARK_SLICE_MULTIPLIER; } else { millis = defaultSliceBudgetMS(); diff --git a/js/src/gc/Scheduling.cpp b/js/src/gc/Scheduling.cpp index eacc6c1f0e36..3d2cbf6c2165 100644 --- a/js/src/gc/Scheduling.cpp +++ b/js/src/gc/Scheduling.cpp @@ -44,6 +44,7 @@ GCSchedulingTunables::GCSchedulingTunables() gcZoneAllocThresholdBase_(TuningDefaults::GCZoneAllocThresholdBase), nonIncrementalFactor_(TuningDefaults::NonIncrementalFactor), zoneAllocDelayBytes_(TuningDefaults::ZoneAllocDelayBytes), + dynamicHeapGrowthEnabled_(TuningDefaults::DynamicHeapGrowthEnabled), highFrequencyThreshold_( TimeDuration::FromSeconds(TuningDefaults::HighFrequencyThreshold)), highFrequencyLowLimitBytes_(TuningDefaults::HighFrequencyLowLimitBytes), @@ -51,6 +52,7 @@ GCSchedulingTunables::GCSchedulingTunables() highFrequencyHeapGrowthMax_(TuningDefaults::HighFrequencyHeapGrowthMax), highFrequencyHeapGrowthMin_(TuningDefaults::HighFrequencyHeapGrowthMin), lowFrequencyHeapGrowth_(TuningDefaults::LowFrequencyHeapGrowth), + dynamicMarkSliceEnabled_(TuningDefaults::DynamicMarkSliceEnabled), minEmptyChunkCount_(TuningDefaults::MinEmptyChunkCount), maxEmptyChunkCount_(TuningDefaults::MaxEmptyChunkCount), nurseryFreeThresholdForIdleCollection_( @@ -130,6 +132,12 @@ bool GCSchedulingTunables::setParameter(JSGCParamKey key, uint32_t value, setLowFrequencyHeapGrowth(newGrowth); break; } + case JSGC_DYNAMIC_HEAP_GROWTH: + dynamicHeapGrowthEnabled_ = value != 0; + break; + case JSGC_DYNAMIC_MARK_SLICE: + dynamicMarkSliceEnabled_ = value != 0; + break; case JSGC_ALLOCATION_THRESHOLD: gcZoneAllocThresholdBase_ = value * 1024 * 1024; break; @@ -283,6 +291,12 @@ void GCSchedulingTunables::resetParameter(JSGCParamKey key, case JSGC_LOW_FREQUENCY_HEAP_GROWTH: setLowFrequencyHeapGrowth(TuningDefaults::LowFrequencyHeapGrowth); break; + case JSGC_DYNAMIC_HEAP_GROWTH: + dynamicHeapGrowthEnabled_ = TuningDefaults::DynamicHeapGrowthEnabled; + break; + case JSGC_DYNAMIC_MARK_SLICE: + dynamicMarkSliceEnabled_ = TuningDefaults::DynamicMarkSliceEnabled; + break; case JSGC_ALLOCATION_THRESHOLD: gcZoneAllocThresholdBase_ = TuningDefaults::GCZoneAllocThresholdBase; break; @@ -356,6 +370,10 @@ void HeapThreshold::setSliceThreshold(ZoneAllocator* zone, float GCHeapThreshold::computeZoneHeapGrowthFactorForHeapSize( size_t lastBytes, const GCSchedulingTunables& tunables, const GCSchedulingState& state) { + if (!tunables.isDynamicHeapGrowthEnabled()) { + return 3.0f; + } + // For small zones, our collection heuristics do not matter much: favor // something simple in this case. if (lastBytes < 1 * 1024 * 1024) { diff --git a/js/src/gc/Scheduling.h b/js/src/gc/Scheduling.h index f7910e48ca7b..4f94b5bf9bd9 100644 --- a/js/src/gc/Scheduling.h +++ b/js/src/gc/Scheduling.h @@ -357,6 +357,9 @@ static const float NonIncrementalFactor = 1.12f; /* JSGC_ZONE_ALLOC_DELAY_KB */ static const size_t ZoneAllocDelayBytes = 1024 * 1024; +/* JSGC_DYNAMIC_HEAP_GROWTH */ +static const bool DynamicHeapGrowthEnabled = false; + /* JSGC_HIGH_FREQUENCY_TIME_LIMIT */ static const auto HighFrequencyThreshold = 1; // in seconds @@ -375,6 +378,9 @@ static const float HighFrequencyHeapGrowthMin = 1.5f; /* JSGC_LOW_FREQUENCY_HEAP_GROWTH */ static const float LowFrequencyHeapGrowth = 1.5f; +/* JSGC_DYNAMIC_MARK_SLICE */ +static const bool DynamicMarkSliceEnabled = false; + /* JSGC_MIN_EMPTY_CHUNK_COUNT */ static const uint32_t MinEmptyChunkCount = 1; @@ -461,6 +467,14 @@ class GCSchedulingTunables { */ UnprotectedData zoneAllocDelayBytes_; + /* + * JSGC_DYNAMIC_HEAP_GROWTH + * + * Totally disables |highFrequencyGC|, the HeapGrowthFactor, and other + * tunables that make GC non-deterministic. + */ + MainThreadOrGCTaskData dynamicHeapGrowthEnabled_; + /* * JSGC_HIGH_FREQUENCY_TIME_LIMIT * @@ -491,6 +505,13 @@ class GCSchedulingTunables { */ MainThreadOrGCTaskData lowFrequencyHeapGrowth_; + /* + * JSGC_DYNAMIC_MARK_SLICE + * + * Doubles the length of IGC slices when in the |highFrequencyGC| mode. + */ + MainThreadData dynamicMarkSliceEnabled_; + /* * JSGC_MIN_EMPTY_CHUNK_COUNT * JSGC_MAX_EMPTY_CHUNK_COUNT @@ -560,6 +581,7 @@ class GCSchedulingTunables { size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; } double nonIncrementalFactor() const { return nonIncrementalFactor_; } size_t zoneAllocDelayBytes() const { return zoneAllocDelayBytes_; } + bool isDynamicHeapGrowthEnabled() const { return dynamicHeapGrowthEnabled_; } const mozilla::TimeDuration& highFrequencyThreshold() const { return highFrequencyThreshold_; } @@ -576,6 +598,7 @@ class GCSchedulingTunables { return highFrequencyHeapGrowthMin_; } double lowFrequencyHeapGrowth() const { return lowFrequencyHeapGrowth_; } + bool isDynamicMarkSliceEnabled() const { return dynamicMarkSliceEnabled_; } unsigned minEmptyChunkCount(const AutoLockGC&) const { return minEmptyChunkCount_; } @@ -635,11 +658,9 @@ class GCSchedulingState { void updateHighFrequencyMode(const mozilla::TimeStamp& lastGCTime, const mozilla::TimeStamp& currentTime, const GCSchedulingTunables& tunables) { -#ifndef JS_MORE_DETERMINISTIC inHighFrequencyGCMode_ = - !lastGCTime.IsNull() && + tunables.isDynamicHeapGrowthEnabled() && !lastGCTime.IsNull() && lastGCTime + tunables.highFrequencyThreshold() > currentTime; -#endif } }; diff --git a/js/src/jit-test/tests/gc/gcparam.js b/js/src/jit-test/tests/gc/gcparam.js index 826349080d91..f3327a9a688c 100644 --- a/js/src/jit-test/tests/gc/gcparam.js +++ b/js/src/jit-test/tests/gc/gcparam.js @@ -44,6 +44,8 @@ testChangeParam("highFrequencyHighLimit"); testChangeParam("highFrequencyHeapGrowthMax"); testChangeParam("highFrequencyHeapGrowthMin"); testChangeParam("lowFrequencyHeapGrowth"); +testChangeParam("dynamicHeapGrowth"); +testChangeParam("dynamicMarkSlice"); testChangeParam("allocationThreshold"); testChangeParam("nonIncrementalFactor"); testChangeParam("minEmptyChunkCount"); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 63929446f3db..2bd75256fcd7 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -11785,13 +11785,21 @@ int main(int argc, char** argv, char** envp) { EnvironmentPreparer environmentPreparer(cx); - if (!op.getBoolOption("no-incremental-gc")) { - JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_ZONE_INCREMENTAL); - JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 10); - } + JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_ZONE_INCREMENTAL); JS::SetProcessLargeAllocationFailureCallback(my_LargeAllocFailCallback); + // Set some parameters to allow incremental GC in low memory conditions, + // as is done for the browser, except in more-deterministic builds or when + // disabled by command line options. +#ifndef JS_MORE_DETERMINISTIC + if (!op.getBoolOption("no-incremental-gc")) { + JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, 1); + JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, 1); + JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 10); + } +#endif + js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback); JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 4382e6fd774f..cb439f95af2b 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1189,6 +1189,14 @@ pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 150); // JSGC_LOW_FREQUENCY_HEAP_GROWTH pref("javascript.options.mem.gc_low_frequency_heap_growth", 150); +// JSGC_DYNAMIC_HEAP_GROWTH +// Override SpiderMonkey default (false). +pref("javascript.options.mem.gc_dynamic_heap_growth", true); + +// JSGC_DYNAMIC_MARK_SLICE +// Override SpiderMonkey default (false). +pref("javascript.options.mem.gc_dynamic_mark_slice", true); + // JSGC_ALLOCATION_THRESHOLD pref("javascript.options.mem.gc_allocation_threshold_mb", 27);