Bug 1392511 - Report the correct information for used bytes. r=jonco
authorPaul Bone <pbone@mozilla.com>
Fri, 25 Aug 2017 15:03:24 +1000
changeset 378865 55e08f65ac23b380ca995319032f07121d46bf71
parent 378864 14e59ce2ce01091411db34aa054f9f4a78f75430
child 378866 9296a073916948eb7c88f1025aae04072c57dc66
push id32443
push userarchaeopteryx@coole-files.de
push dateTue, 05 Sep 2017 09:41:20 +0000
treeherdermozilla-central@3ecda4678c49 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1392511
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1392511 - Report the correct information for used bytes. r=jonco Only use the promotion rate to make pre tenuring and nursery size decisions (now that it is calculated correctly and not under-estimated) if the nursery is at least 90% full.
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -460,16 +460,32 @@ js::TenuringTracer::TenuringTracer(JSRun
   : JSTracer(rt, JSTracer::TracerKindTag::Tenuring, TraceWeakMapKeysValues)
   , nursery_(*nursery)
   , tenuredSize(0)
   , head(nullptr)
   , tail(&head)
 {
 }
 
+inline float
+js::Nursery::calcPromotionRate(bool *validForTenuring) const {
+    float used = float(previousGC.nurseryUsedBytes);
+    float capacity = float(previousGC.nurseryCapacity);
+    float tenured = float(previousGC.tenuredBytes);
+
+    if (validForTenuring) {
+        /*
+         * We can only use promotion rates if they're likely to be valid,
+         * they're only valid if the nursury was at least 90% full.
+         */
+        *validForTenuring = used > capacity * 0.9f;
+    }
+    return tenured / used;
+}
+
 void
 js::Nursery::renderProfileJSON(JSONPrinter& json) const
 {
     if (!isEnabled()) {
         json.beginObject();
         json.property("status", "nursery disabled");
         json.endObject();
         return;
@@ -485,18 +501,17 @@ js::Nursery::renderProfileJSON(JSONPrint
         json.endObject();
         return;
     }
 
     json.beginObject();
 
     json.property("reason", JS::gcreason::ExplainReason(previousGC.reason));
     json.property("bytes_tenured", previousGC.tenuredBytes);
-    json.floatProperty("promotion_rate",
-                       100.0 * previousGC.tenuredBytes / double(previousGC.nurseryUsedBytes), 2);
+    json.floatProperty("promotion_rate", calcPromotionRate(nullptr), 0);
     json.property("nursery_bytes", previousGC.nurseryUsedBytes);
     json.property("new_nursery_bytes", numChunks() * ChunkSize);
 
     json.beginObjectProperty("timings");
 
 #define EXTRACT_NAME(name, text) #name,
     static const char* names[] = {
 FOR_EACH_NURSERY_PROFILE_TIME(EXTRACT_NAME)
@@ -604,41 +619,49 @@ js::Nursery::collect(JS::gcreason::Reaso
     maybeClearProfileDurations();
     startProfile(ProfileKey::Total);
 
     // The hazard analysis thinks doCollection can invalidate pointers in
     // tenureCounts below.
     JS::AutoSuppressGCAnalysis nogc;
 
     TenureCountCache tenureCounts;
-    double promotionRate = 0;
     previousGC.reason = JS::gcreason::NO_REASON;
-    if (!isEmpty())
-        promotionRate = doCollection(reason, tenureCounts);
+    if (!isEmpty()) {
+        doCollection(reason, tenureCounts);
+    } else {
+        previousGC.nurseryUsedBytes = 0;
+        previousGC.nurseryCapacity = spaceToEnd();
+        previousGC.tenuredBytes = 0;
+    }
 
     // Resize the nursery.
     startProfile(ProfileKey::Resize);
-    maybeResizeNursery(reason, promotionRate);
+    maybeResizeNursery(reason);
     endProfile(ProfileKey::Resize);
 
     // If we are promoting the nursery, or exhausted the store buffer with
     // pointers to nursery things, which will force a collection well before
     // the nursery is full, look for object groups that are getting promoted
     // excessively and try to pretenure them.
     startProfile(ProfileKey::Pretenure);
+    bool validPromotionRate;
+    const float promotionRate = calcPromotionRate(&validPromotionRate);
     uint32_t pretenureCount = 0;
-    if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) {
-        JSContext* cx = TlsContext.get();
-        for (auto& entry : tenureCounts.entries) {
-            if (entry.count >= 3000) {
-                ObjectGroup* group = entry.group;
-                if (group->canPreTenure()) {
-                    AutoCompartment ac(cx, group);
-                    group->setShouldPreTenure(cx);
-                    pretenureCount++;
+    if (validPromotionRate) {
+        if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) {
+            JSContext* cx = TlsContext.get();
+            for (auto& entry : tenureCounts.entries) {
+                if (entry.count >= 3000) {
+                    ObjectGroup* group = entry.group;
+                    if (group->canPreTenure()) {
+                        AutoCompartment ac(cx, group);
+                        group->setShouldPreTenure(cx);
+                        pretenureCount++;
+                    }
                 }
             }
         }
     }
     endProfile(ProfileKey::Pretenure);
 
     // We ignore gcMaxBytes when allocating for minor collection. However, if we
     // overflowed, we disable the nursery. The next time we allocate, we'll fail
@@ -680,28 +703,29 @@ js::Nursery::collect(JS::gcreason::Reaso
                     fprintf(stderr, "  %d x ", entry.count);
                     entry.group->print();
                 }
             }
         }
     }
 }
 
-double
+void
 js::Nursery::doCollection(JS::gcreason::Reason reason,
                           TenureCountCache& tenureCounts)
 {
     JSRuntime* rt = runtime();
     AutoTraceSession session(rt, JS::HeapState::MinorCollecting);
     AutoSetThreadIsPerformingGC performingGC;
     AutoStopVerifyingBarriers av(rt, false);
     AutoDisableProxyCheck disableStrictProxyChecking;
     mozilla::DebugOnly<AutoEnterOOMUnsafeRegion> oomUnsafeRegion;
 
-    size_t initialNurserySize = spaceToEnd();
+    const size_t initialNurseryCapacity = spaceToEnd();
+    const size_t initialNurseryUsedBytes = initialNurseryCapacity - freeSpace();
 
     // Move objects pointed to by roots from the nursery to the major heap.
     TenuringTracer mover(rt, this);
 
     // Mark the store buffer. This must happen first.
     StoreBuffer& sb = runtime()->gc.storeBuffer();
 
     // The MIR graph only contains nursery pointers if cancelIonCompilations()
@@ -788,21 +812,19 @@ js::Nursery::doCollection(JS::gcreason::
     startProfile(ProfileKey::CheckHashTables);
 #ifdef JS_GC_ZEAL
     if (rt->hasZealMode(ZealMode::CheckHashTablesOnMinorGC))
         CheckHashTablesAfterMovingGC(rt);
 #endif
     endProfile(ProfileKey::CheckHashTables);
 
     previousGC.reason = reason;
-    previousGC.nurseryUsedBytes = initialNurserySize;
+    previousGC.nurseryCapacity = initialNurseryCapacity;
+    previousGC.nurseryUsedBytes = initialNurseryUsedBytes;
     previousGC.tenuredBytes = mover.tenuredSize;
-
-    // Calculate and return the promotion rate.
-    return mover.tenuredSize / double(initialNurserySize);
 }
 
 void
 js::Nursery::FreeMallocedBuffersTask::transferBuffersToFree(MallocedBuffersSet& buffersToFree,
                                                             const AutoLockHelperThreadState& lock)
 {
     // Transfer the contents of the source set to the task's buffers_ member by
     // swapping the sets, which also clears the source.
@@ -918,17 +940,17 @@ js::Nursery::setCurrentChunk(unsigned ch
 MOZ_ALWAYS_INLINE void
 js::Nursery::setStartPosition()
 {
     currentStartChunk_ = currentChunk_;
     currentStartPosition_ = position();
 }
 
 void
-js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate)
+js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason)
 {
     static const double GrowThreshold   = 0.05;
     static const double ShrinkThreshold = 0.01;
     unsigned newMaxNurseryChunks;
 
     // Shrink the nursery to its minimum size of we ran out of memory or
     // received a memory pressure event.
     if (gc::IsOOMReason(reason)) {
@@ -937,30 +959,37 @@ js::Nursery::maybeResizeNursery(JS::gcre
     }
 
 #ifdef JS_GC_ZEAL
     // This zeal mode disabled nursery resizing.
     if (runtime()->hasZealMode(ZealMode::GenerationalGC))
         return;
 #endif
 
+    bool canUsePromotionRate;
+    const float promotionRate = calcPromotionRate(&canUsePromotionRate);
+
     newMaxNurseryChunks = runtime()->gc.tunables.gcMaxNurseryBytes() >> ChunkShift;
     if (newMaxNurseryChunks != maxNurseryChunks_) {
         maxNurseryChunks_ = newMaxNurseryChunks;
         /* The configured maximum nursery size is changing */
-        int extraChunks = numChunks() - newMaxNurseryChunks;
+        const int extraChunks = numChunks() - newMaxNurseryChunks;
         if (extraChunks > 0) {
             /* We need to shrink the nursery */
             shrinkAllocableSpace(extraChunks);
 
-            previousPromotionRate_ = promotionRate;
+            if (canUsePromotionRate)
+                previousPromotionRate_ = promotionRate;
             return;
         }
     }
 
+    if (!canUsePromotionRate)
+        return;
+
     if (promotionRate > GrowThreshold)
         growAllocableSpace();
     else if (promotionRate < ShrinkThreshold && previousPromotionRate_ < ShrinkThreshold)
         shrinkAllocableSpace(1);
 
     previousPromotionRate_ = promotionRate;
 }
 
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -311,17 +311,17 @@ class Nursery
 
     /* The index of the chunk that is currently being allocated from. */
     unsigned currentChunk_;
 
     /* Maximum number of chunks to allocate for the nursery. */
     unsigned maxNurseryChunks_;
 
     /* Promotion rate for the previous minor collection. */
-    double previousPromotionRate_;
+    float previousPromotionRate_;
 
     /* Report minor collections taking at least this long, if enabled. */
     mozilla::TimeDuration profileThreshold_;
     bool enableProfiling_;
 
     /* Report ObjectGroups with at lest this many instances tenured. */
     int64_t reportTenurings_;
 
@@ -348,23 +348,39 @@ class Nursery
     using ProfileDurations =
         mozilla::EnumeratedArray<ProfileKey, ProfileKey::KeyCount, mozilla::TimeDuration>;
 
     ProfileTimes startTimes_;
     ProfileDurations profileDurations_;
     ProfileDurations totalDurations_;
     uint64_t minorGcCount_;
 
+    /*
+     * This data is initialised only if the nursery is enabled and after at
+     * least one call to Nursery::collect()
+     */
     struct {
         JS::gcreason::Reason reason;
-        uint64_t nurseryUsedBytes;
-        uint64_t tenuredBytes;
+        size_t nurseryCapacity;
+        size_t nurseryUsedBytes;
+        size_t tenuredBytes;
     } previousGC;
 
     /*
+     * Calculate the promotion rate of the most recent minor GC.
+     * The valid_for_tenuring parameter is used to return whether this
+     * promotion rate is accurate enough (the nursery was full enough) to be
+     * used for tenuring and other decisions.
+     *
+     * Must only be called if the previousGC data is initialised.
+     */
+    float
+    calcPromotionRate(bool *validForTenuring) const;
+
+    /*
      * The set of externally malloced buffers potentially kept live by objects
      * stored in the nursery. Any external buffers that do not belong to a
      * tenured thing at the end of a minor GC must be freed.
      */
     typedef HashSet<void*, PointerHasher<void*>, SystemAllocPolicy> MallocedBuffersSet;
     MallocedBuffersSet mallocedBuffers;
 
     /* A task structure used to free the malloced bufers on a background thread. */
@@ -433,17 +449,17 @@ class Nursery
     JSRuntime* runtime() const { return runtime_; }
 
     /* Allocates a new GC thing from the tenured generation during minor GC. */
     gc::TenuredCell* allocateFromTenured(JS::Zone* zone, gc::AllocKind thingKind);
 
     /* Common internal allocator function. */
     void* allocate(size_t size);
 
-    double doCollection(JS::gcreason::Reason reason,
+    void doCollection(JS::gcreason::Reason reason,
                         gc::TenureCountCache& tenureCounts);
 
     /*
      * Move the object at |src| in the Nursery to an already-allocated cell
      * |dst| in Tenured.
      */
     void collectToFixedPoint(TenuringTracer& trc, gc::TenureCountCache& tenureCounts);
 
@@ -461,17 +477,17 @@ class Nursery
      * Frees all non-live nursery-allocated things at the end of a minor
      * collection.
      */
     void sweep();
 
     void sweepDictionaryModeObjects();
 
     /* Change the allocable space provided by the nursery. */
-    void maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate);
+    void maybeResizeNursery(JS::gcreason::Reason reason);
     void growAllocableSpace();
     void shrinkAllocableSpace(unsigned removeNumChunks);
     void minimizeAllocableSpace();
 
     /* Profile recording and printing. */
     void maybeClearProfileDurations();
     void startProfile(ProfileKey key);
     void endProfile(ProfileKey key);