Bug 1384049 - Trigger an incremental GC based on malloc memory counters r=pbone
authorJon Coppeard <jcoppeard@mozilla.com>
Wed, 04 Oct 2017 15:45:30 +0100
changeset 428187 e0c112aed854cc1a44cb5a00553debf85ab74ed0
parent 428186 34557043bd33edddbe98e158d4f1dfcabfbb3f37
child 428188 22f087c0c507625ff48b762d752bd55ca91bccc7
push id97
push userfmarier@mozilla.com
push dateSat, 14 Oct 2017 01:12:59 +0000
reviewerspbone
bugs1384049
milestone58.0a1
Bug 1384049 - Trigger an incremental GC based on malloc memory counters r=pbone
js/src/gc/Allocator.cpp
js/src/gc/GCRuntime.h
js/src/jsgc.cpp
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -423,17 +423,17 @@ GCRuntime::allocateArena(Chunk* chunk, Z
     if (checkThresholds && usage.gcBytes() >= tunables.gcMaxBytes())
         return nullptr;
 
     Arena* arena = chunk->allocateArena(rt, zone, thingKind, lock);
     zone->usage.addGCArena();
 
     // Trigger an incremental slice if needed.
     if (checkThresholds)
-        maybeAllocTriggerZoneGC(zone, lock);
+        maybeAllocTriggerGC(zone, lock);
 
     return arena;
 }
 
 Arena*
 Chunk::allocateArena(JSRuntime* rt, Zone* zone, AllocKind thingKind, const AutoLockGC& lock)
 {
     Arena* arena = info.numArenasFreeCommitted > 0
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -162,24 +162,24 @@ class GCSchedulingTunables
      */
     ActiveThreadOrGCTaskData<size_t> gcZoneAllocThresholdBase_;
 
     /*
      * JSGC_ALLOCATION_THRESHOLD_FACTOR
      *
      * Fraction of threshold.gcBytes() which triggers an incremental GC.
      */
-    UnprotectedData<float> zoneAllocThresholdFactor_;
+    UnprotectedData<float> allocThresholdFactor_;
 
     /*
      * JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT
      *
      * The same except when doing so would interrupt an already running GC.
      */
-    UnprotectedData<float> zoneAllocThresholdFactorAvoidInterrupt_;
+    UnprotectedData<float> allocThresholdFactorAvoidInterrupt_;
 
     /*
      * Number of bytes to allocate between incremental slices in GCs triggered
      * by the zone allocation threshold.
      *
      * This value does not have a JSGCParamKey parameter yet.
      */
     UnprotectedData<size_t> zoneAllocDelayBytes_;
@@ -246,18 +246,18 @@ class GCSchedulingTunables
     UnprotectedData<uint32_t> maxEmptyChunkCount_;
 
   public:
     GCSchedulingTunables();
 
     size_t gcMaxBytes() const { return gcMaxBytes_; }
     size_t gcMaxNurseryBytes() const { return gcMaxNurseryBytes_; }
     size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; }
-    float zoneAllocThresholdFactor() const { return zoneAllocThresholdFactor_; }
-    float zoneAllocThresholdFactorAvoidInterrupt() const { return zoneAllocThresholdFactorAvoidInterrupt_; }
+    float allocThresholdFactor() const { return allocThresholdFactor_; }
+    float allocThresholdFactorAvoidInterrupt() const { return allocThresholdFactorAvoidInterrupt_; }
     size_t zoneAllocDelayBytes() const { return zoneAllocDelayBytes_; }
     bool isDynamicHeapGrowthEnabled() const { return dynamicHeapGrowthEnabled_; }
     uint64_t highFrequencyThresholdUsec() const { return highFrequencyThresholdUsec_; }
     uint64_t highFrequencyLowLimitBytes() const { return highFrequencyLowLimitBytes_; }
     uint64_t highFrequencyHighLimitBytes() const { return highFrequencyHighLimitBytes_; }
     double highFrequencyHeapGrowthMax() const { return highFrequencyHeapGrowthMax_; }
     double highFrequencyHeapGrowthMin() const { return highFrequencyHeapGrowthMin_; }
     double lowFrequencyHeapGrowth() const { return lowFrequencyHeapGrowth_; }
@@ -740,17 +740,17 @@ class GCRuntime
     void removeRoot(Value* vp);
     void setMarkStackLimit(size_t limit, AutoLockGC& lock);
 
     MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock);
     void resetParameter(JSGCParamKey key, AutoLockGC& lock);
     uint32_t getParameter(JSGCParamKey key, const AutoLockGC& lock);
 
     MOZ_MUST_USE bool triggerGC(JS::gcreason::Reason reason);
-    void maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock);
+    void maybeAllocTriggerGC(Zone* zone, const AutoLockGC& lock);
     // The return value indicates if we were able to do the GC.
     bool triggerZoneGC(Zone* zone, JS::gcreason::Reason reason,
                        size_t usedBytes, size_t thresholdBytes);
     void maybeGC(Zone* zone);
     // The return value indicates whether a major GC was performed.
     bool gcIfRequested();
     void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
     void startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -279,20 +279,20 @@ namespace TuningDefaults {
 
     /* JSGC_ALLOCATION_THRESHOLD */
     static const size_t GCZoneAllocThresholdBase = 30 * 1024 * 1024;
 
     /* JSGC_MAX_MALLOC_BYTES */
     static const size_t MaxMallocBytes = 128 * 1024 * 1024;
 
     /* JSGC_ALLOCATION_THRESHOLD_FACTOR */
-    static const float ZoneAllocThresholdFactor = 0.9f;
+    static const float AllocThresholdFactor = 0.9f;
 
     /* JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT */
-    static const float ZoneAllocThresholdFactorAvoidInterrupt = 0.9f;
+    static const float AllocThresholdFactorAvoidInterrupt = 0.9f;
 
     /* no parameter */
     static const size_t ZoneAllocDelayBytes = 1024 * 1024;
 
     /* JSGC_DYNAMIC_HEAP_GROWTH */
     static const bool DynamicHeapGrowthEnabled = false;
 
     /* JSGC_HIGH_FREQUENCY_TIME_LIMIT */
@@ -1341,24 +1341,24 @@ GCSchedulingTunables::setParameter(JSGCP
         break;
       case JSGC_ALLOCATION_THRESHOLD:
         gcZoneAllocThresholdBase_ = value * 1024 * 1024;
         break;
       case JSGC_ALLOCATION_THRESHOLD_FACTOR: {
         float newFactor = value / 100.0;
         if (newFactor <= 0.1 || newFactor > 1.0)
             return false;
-        zoneAllocThresholdFactor_ = newFactor;
+        allocThresholdFactor_ = newFactor;
         break;
       }
       case JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT: {
         float newFactor = value / 100.0;
         if (newFactor <= 0.1 || newFactor > 1.0)
             return false;
-        zoneAllocThresholdFactorAvoidInterrupt_ = newFactor;
+        allocThresholdFactorAvoidInterrupt_ = newFactor;
         break;
       }
       case JSGC_MIN_EMPTY_CHUNK_COUNT:
         setMinEmptyChunkCount(value);
         break;
       case JSGC_MAX_EMPTY_CHUNK_COUNT:
         setMaxEmptyChunkCount(value);
         break;
@@ -1407,19 +1407,18 @@ GCSchedulingTunables::setMaxEmptyChunkCo
         minEmptyChunkCount_ = maxEmptyChunkCount_;
     MOZ_ASSERT(maxEmptyChunkCount_ >= minEmptyChunkCount_);
 }
 
 GCSchedulingTunables::GCSchedulingTunables()
   : gcMaxBytes_(0),
     gcMaxNurseryBytes_(0),
     gcZoneAllocThresholdBase_(TuningDefaults::GCZoneAllocThresholdBase),
-    zoneAllocThresholdFactor_(TuningDefaults::ZoneAllocThresholdFactor),
-    zoneAllocThresholdFactorAvoidInterrupt_(
-        TuningDefaults::ZoneAllocThresholdFactorAvoidInterrupt),
+    allocThresholdFactor_(TuningDefaults::AllocThresholdFactor),
+    allocThresholdFactorAvoidInterrupt_(TuningDefaults::AllocThresholdFactorAvoidInterrupt),
     zoneAllocDelayBytes_(TuningDefaults::ZoneAllocDelayBytes),
     dynamicHeapGrowthEnabled_(TuningDefaults::DynamicHeapGrowthEnabled),
     highFrequencyThresholdUsec_(TuningDefaults::HighFrequencyThresholdUsec),
     highFrequencyLowLimitBytes_(TuningDefaults::HighFrequencyLowLimitBytes),
     highFrequencyHighLimitBytes_(TuningDefaults::HighFrequencyHighLimitBytes),
     highFrequencyHeapGrowthMax_(TuningDefaults::HighFrequencyHeapGrowthMax),
     highFrequencyHeapGrowthMin_(TuningDefaults::HighFrequencyHeapGrowthMin),
     lowFrequencyHeapGrowth_(TuningDefaults::LowFrequencyHeapGrowth),
@@ -1496,21 +1495,20 @@ GCSchedulingTunables::resetParameter(JSG
         break;
       case JSGC_DYNAMIC_MARK_SLICE:
         dynamicMarkSliceEnabled_ = TuningDefaults::DynamicMarkSliceEnabled;
         break;
       case JSGC_ALLOCATION_THRESHOLD:
         gcZoneAllocThresholdBase_ = TuningDefaults::GCZoneAllocThresholdBase;
         break;
       case JSGC_ALLOCATION_THRESHOLD_FACTOR:
-        zoneAllocThresholdFactor_ = TuningDefaults::ZoneAllocThresholdFactor;
+        allocThresholdFactor_ = TuningDefaults::AllocThresholdFactor;
         break;
       case JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT:
-        zoneAllocThresholdFactorAvoidInterrupt_ =
-            TuningDefaults::ZoneAllocThresholdFactorAvoidInterrupt;
+        allocThresholdFactorAvoidInterrupt_ = TuningDefaults::AllocThresholdFactorAvoidInterrupt;
         break;
       case JSGC_MIN_EMPTY_CHUNK_COUNT:
         setMinEmptyChunkCount(TuningDefaults::MinEmptyChunkCount);
         break;
       case JSGC_MAX_EMPTY_CHUNK_COUNT:
         setMaxEmptyChunkCount(TuningDefaults::MaxEmptyChunkCount);
         break;
       case JSGC_REFRESH_FRAME_SLICES_ENABLED:
@@ -1563,19 +1561,19 @@ GCRuntime::getParameter(JSGCParamKey key
         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_ALLOCATION_THRESHOLD_FACTOR:
-        return uint32_t(tunables.zoneAllocThresholdFactor() * 100);
+        return uint32_t(tunables.allocThresholdFactor() * 100);
       case JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT:
-        return uint32_t(tunables.zoneAllocThresholdFactorAvoidInterrupt() * 100);
+        return uint32_t(tunables.allocThresholdFactorAvoidInterrupt() * 100);
       case JSGC_MIN_EMPTY_CHUNK_COUNT:
         return tunables.minEmptyChunkCount(lock);
       case JSGC_MAX_EMPTY_CHUNK_COUNT:
         return tunables.maxEmptyChunkCount();
       case JSGC_COMPACTING_ENABLED:
         return compactingEnabled;
       case JSGC_REFRESH_FRAME_SLICES_ENABLED:
         return tunables.areRefreshFrameSlicesEnabled();
@@ -3159,66 +3157,85 @@ GCRuntime::triggerGC(JS::gcreason::Reaso
         return false;
 
     JS::PrepareForFullGC(rt->activeContextFromOwnThread());
     requestMajorGC(reason);
     return true;
 }
 
 void
-GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock)
-{
+GCRuntime::maybeAllocTriggerGC(Zone* zone, const AutoLockGC& lock)
+{
+    MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting());
+
+    if (!CurrentThreadCanAccessRuntime(rt)) {
+        // Zones in use by a helper thread can't be collected.
+        MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
+        return;
+    }
+
+    // Check GC bytes triggers.
+
     size_t usedBytes = zone->usage.gcBytes();
     size_t thresholdBytes = zone->threshold.gcTriggerBytes();
 
-    if (!CurrentThreadCanAccessRuntime(rt)) {
-        /* Zones in use by a helper thread can't be collected. */
-        MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
-        return;
-    }
-
     if (usedBytes >= thresholdBytes) {
-        /*
-         * The threshold has been surpassed, immediately trigger a GC,
-         * which will be done non-incrementally.
-         */
+        // The threshold has been surpassed, immediately trigger a GC, which
+        // will be done non-incrementally.
         triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER, usedBytes, thresholdBytes);
-    } else {
-        bool wouldInterruptCollection;
-        size_t igcThresholdBytes;
-        float zoneAllocThresholdFactor;
-
-        wouldInterruptCollection = isIncrementalGCInProgress() &&
-            !zone->isCollecting();
-        zoneAllocThresholdFactor = wouldInterruptCollection ?
-            tunables.zoneAllocThresholdFactorAvoidInterrupt() :
-            tunables.zoneAllocThresholdFactor();
-
-        igcThresholdBytes = thresholdBytes * zoneAllocThresholdFactor;
-
-        if (usedBytes >= igcThresholdBytes) {
-            // Reduce the delay to the start of the next incremental slice.
-            if (zone->gcDelayBytes < ArenaSize)
-                zone->gcDelayBytes = 0;
-            else
-                zone->gcDelayBytes -= ArenaSize;
-
-            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::ALLOC_TRIGGER, usedBytes, igcThresholdBytes);
-
-                // Delay the next slice until a certain amount of allocation
-                // has been performed.
-                zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
-            }
-        }
-    }
+        return;
+    }
+
+    bool wouldInterruptCollection = isIncrementalGCInProgress() && !zone->isCollecting();
+    float zoneGCThresholdFactor =
+        wouldInterruptCollection ? tunables.allocThresholdFactorAvoidInterrupt()
+                                 : tunables.allocThresholdFactor();
+
+    size_t igcThresholdBytes = thresholdBytes * zoneGCThresholdFactor;
+
+    if (usedBytes >= igcThresholdBytes) {
+        // Reduce the delay to the start of the next incremental slice.
+        if (zone->gcDelayBytes < ArenaSize)
+            zone->gcDelayBytes = 0;
+        else
+            zone->gcDelayBytes -= ArenaSize;
+
+        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::ALLOC_TRIGGER, usedBytes, igcThresholdBytes);
+
+            // Delay the next slice until a certain amount of allocation
+            // has been performed.
+            zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
+            return;
+        }
+    }
+
+    // Check malloc bytes triggers.
+
+    wouldInterruptCollection = isIncrementalGCInProgress() && !isFull;
+    float fullGCThresholdFactor =
+        wouldInterruptCollection ? tunables.allocThresholdFactorAvoidInterrupt()
+                                 : tunables.allocThresholdFactor();
+
+    size_t mallocBytes = mallocCounter.bytes();
+    size_t mallocThesholdBytes = mallocCounter.maxBytes() * fullGCThresholdFactor;
+    if (mallocBytes > mallocThesholdBytes) {
+        stats().recordTrigger(mallocBytes, mallocThesholdBytes);
+        MOZ_ALWAYS_TRUE(triggerGC(JS::gcreason::TOO_MUCH_MALLOC));
+        return;
+    }
+
+    mallocBytes = zone->GCMallocBytes();
+    mallocThesholdBytes = zone->GCMaxMallocBytes() * zoneGCThresholdFactor;
+    if (mallocBytes > mallocThesholdBytes)
+        triggerZoneGC(zone, JS::gcreason::TOO_MUCH_MALLOC, mallocBytes, mallocThesholdBytes);
 }
 
 bool
 GCRuntime::triggerZoneGC(Zone* zone, JS::gcreason::Reason reason, size_t used, size_t threshold)
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     /* GC is already running. */
@@ -7531,17 +7548,17 @@ GCRuntime::minorGC(JS::gcreason::Reason 
 #ifdef JS_GC_ZEAL
     if (rt->hasZealMode(ZealMode::CheckHeapAfterGC))
         CheckHeapAfterGC(rt);
 #endif
 
     {
         AutoLockGC lock(rt);
         for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
-            maybeAllocTriggerZoneGC(zone, lock);
+            maybeAllocTriggerGC(zone, lock);
     }
 }
 
 JS::AutoDisableGenerationalGC::AutoDisableGenerationalGC(JSContext* cx)
   : cx(cx)
 {
     if (!cx->generationalDisabled) {
         cx->runtime()->gc.evictNursery(JS::gcreason::API);
@@ -7732,17 +7749,17 @@ js::NewCompartment(JSContext* cx, JSPrin
 
 void
 gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
 {
     JSRuntime* rt = source->runtimeFromActiveCooperatingThread();
     rt->gc.mergeCompartments(source, target);
 
     AutoLockGC lock(rt);
-    rt->gc.maybeAllocTriggerZoneGC(target->zone(), lock);
+    rt->gc.maybeAllocTriggerGC(target->zone(), lock);
 }
 
 void
 GCRuntime::mergeCompartments(JSCompartment* source, JSCompartment* target)
 {
     // The source compartment must be specifically flagged as mergable.  This
     // also implies that the compartment is not visible to the debugger.
     MOZ_ASSERT(source->creationOptions_.mergeable());