bug 673795 - part1, no empty chunk hashing. r=anygregor
authorIgor Bukanov <igor@mir2.org>
Mon, 25 Jul 2011 13:04:02 +0200
changeset 74368 8c36a72adee981550afeca616c7403a9b4248b78
parent 74367 1127ccbf8f4eebdd06023eb4097bee5c3d62f063
child 74369 3210abdedf8ae17174a28b3120f9f69224a43d5d
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewersanygregor
bugs673795
milestone8.0a1
bug 673795 - part1, no empty chunk hashing. r=anygregor
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsgc.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2713,19 +2713,19 @@ JS_GetGCParameter(JSRuntime *rt, JSGCPar
         return rt->gcMaxMallocBytes;
       case JSGC_STACKPOOL_LIFESPAN:
         return rt->gcEmptyArenaPoolLifespan;
       case JSGC_BYTES:
         return rt->gcBytes;
       case JSGC_MODE:
         return uint32(rt->gcMode);
       case JSGC_UNUSED_CHUNKS:
-        return uint32(rt->gcChunksWaitingToExpire);
+        return uint32(rt->gcEmptyChunkCount);
       case JSGC_TOTAL_CHUNKS:
-        return uint32(rt->gcUserChunkSet.count() + rt->gcSystemChunkSet.count());
+        return uint32(rt->gcUserChunkSet.count() + rt->gcSystemChunkSet.count() + rt->gcEmptyChunkCount);
       default:
         JS_ASSERT(key == JSGC_NUMBER);
         return rt->gcNumber;
     }
 }
 
 JS_PUBLIC_API(void)
 JS_SetGCParameterForThread(JSContext *cx, JSGCParamKey key, uint32 value)
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -133,17 +133,17 @@ struct GSNCache {
 
     jsbytecode      *code;
     Map             map;
 
     GSNCache() : code(NULL) { }
 
     void purge();
 };
- 
+
 inline GSNCache *
 GetGSNCache(JSContext *cx);
 
 struct PendingProxyOperation {
     PendingProxyOperation   *next;
     JSObject                *object;
 };
 
@@ -207,17 +207,17 @@ struct ThreadData {
     PendingProxyOperation *pendingProxyOperation;
 
     ConservativeGCThreadData conservativeGC;
 
     ThreadData();
     ~ThreadData();
 
     bool init();
-    
+
     void mark(JSTracer *trc) {
         stackSpace.mark(trc);
     }
 
     void purge(JSContext *cx) {
         gsnCache.purge();
 
         /* FIXME: bug 506341. */
@@ -258,17 +258,17 @@ struct JSThread {
     /* Factored out of JSThread for !JS_THREADSAFE embedding in JSRuntime. */
     js::ThreadData      data;
 
     JSThread(void *id)
       : id(id),
         suspendCount(0)
 # ifdef DEBUG
       , checkRequestDepth(0)
-# endif        
+# endif
     {
         JS_INIT_CLIST(&contextList);
     }
 
     ~JSThread() {
         /* The thread must have zero contexts. */
         JS_ASSERT(JS_CLIST_IS_EMPTY(&contextList));
     }
@@ -387,26 +387,27 @@ struct JSRuntime
      * This comes early in JSRuntime to minimize the immediate format used by
      * trace-JITted code that reads it.
      */
     uint32              protoHazardShape;
 
     /* Garbage collector state, used by jsgc.c. */
     js::GCChunkSet      gcUserChunkSet;
     js::GCChunkSet      gcSystemChunkSet;
+    js::gc::Chunk       *gcEmptyChunkListHead;
+    size_t              gcEmptyChunkCount;
 
     js::RootedValueMap  gcRootsHash;
     js::GCLocks         gcLocksHash;
     jsrefcount          gcKeepAtoms;
     uint32              gcBytes;
     uint32              gcTriggerBytes;
     size_t              gcLastBytes;
     size_t              gcMaxBytes;
     size_t              gcMaxMallocBytes;
-    size_t              gcChunksWaitingToExpire;
     uint32              gcEmptyArenaPoolLifespan;
     uint32              gcNumber;
     js::GCMarker        *gcMarkingTracer;
     bool                gcChunkAllocationSinceLastGC;
     int64               gcNextFullGCTime;
     int64               gcJitReleaseTime;
     JSGCMode            gcMode;
     volatile bool       gcIsNeeded;
@@ -667,17 +668,17 @@ struct JSRuntime
 
 #ifdef JSGC_TESTPILOT
         bool        infoEnabled;
 
         bool isTimerEnabled() {
             return infoEnabled;
         }
 
-        /* 
+        /*
          * Circular buffer with GC data.
          * count may grow >= INFO_LIMIT, which would indicate data loss.
          */
         static const size_t INFO_LIMIT = 64;
         JSGCInfo    info[INFO_LIMIT];
         size_t      start;
         size_t      count;
 #else /* defined(MOZ_GCTIMER) */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -404,18 +404,31 @@ Chunk::releaseArena(ArenaHeader *aheader
 #endif
     JS_ATOMIC_ADD(&rt->gcBytes, -int32(ArenaSize));
     JS_ATOMIC_ADD(&comp->gcBytes, -int32(ArenaSize));
 
     aheader->setAsNotAllocated();
     aheader->next = info.emptyArenaListHead;
     info.emptyArenaListHead = aheader;
     ++info.numFree;
-    if (unused())
+    if (unused()) {
+        rt->gcUserChunkSet.remove(this);
+        rt->gcSystemChunkSet.remove(this);
+
+        /*
+         * We keep empty chunks until we are done with finalization to allow
+         * calling IsAboutToBeFinalized/Cell::isMarked for finalized GC things
+         * in empty chunks. So we add the chunk to the empty set even during
+         * GC_SHRINK.
+         */
         info.age = 0;
+        info.link = rt->gcEmptyChunkListHead;
+        rt->gcEmptyChunkListHead = this;
+        rt->gcEmptyChunkCount++;
+    }
 }
 
 inline Chunk *
 AllocateGCChunk(JSRuntime *rt)
 {
     Chunk *p = (Chunk *)rt->gcChunkAllocator->alloc();
 #ifdef MOZ_GCTIMER
     if (p)
@@ -443,99 +456,92 @@ PickChunk(JSContext *cx)
 
     JSRuntime *rt = cx->runtime;
     bool isSystemCompartment = cx->compartment->isSystemCompartment;
 
     /*
      * The chunk used for the last allocation is full, search all chunks for
      * free arenas.
      */
-    GCChunkSet::Range
-        r(isSystemCompartment ? rt->gcSystemChunkSet.all() : rt->gcUserChunkSet.all());
-    for (; !r.empty(); r.popFront()) {
+    GCChunkSet *chunkSet = isSystemCompartment ? &rt->gcSystemChunkSet : &rt->gcUserChunkSet;
+    for (GCChunkSet::Range r(chunkSet->all()); !r.empty(); r.popFront()) {
         chunk = r.front();
         if (chunk->hasAvailableArenas()) {
             cx->compartment->chunk = chunk;
             return chunk;
         }
     }
 
-    chunk = AllocateGCChunk(rt);
-    if (!chunk) {
-        /* Our last chance is to find an empty chunk in the other chunk set. */
-        GCChunkSet::Enum e(isSystemCompartment ? rt->gcUserChunkSet : rt->gcSystemChunkSet);
-        for (; !e.empty(); e.popFront()) {
-            if (e.front()->info.numFree == ArenasPerChunk) {
-                chunk = e.front();
-                e.removeFront();
-                break;
-            }
-        }
-
+    /* Use an empty chunk when available or allocate a new one. */
+    chunk = rt->gcEmptyChunkListHead;
+    if (chunk) {
+        JS_ASSERT(chunk->unused());
+        JS_ASSERT(!rt->gcUserChunkSet.has(chunk));
+        JS_ASSERT(!rt->gcSystemChunkSet.has(chunk));
+        JS_ASSERT(rt->gcEmptyChunkCount >= 1);
+        rt->gcEmptyChunkListHead = chunk->info.link;
+        rt->gcEmptyChunkCount--;
+    } else {
+        chunk = AllocateGCChunk(rt);
         if (!chunk)
             return NULL;
+
+        chunk->init(rt);
+        rt->gcChunkAllocationSinceLastGC = true;
     }
 
     /*
      * FIXME bug 583732 - chunk is newly allocated and cannot be present in
      * the table so using ordinary lookupForAdd is suboptimal here.
      */
-    GCChunkSet::AddPtr p = isSystemCompartment ?
-                           rt->gcSystemChunkSet.lookupForAdd(chunk) :
-                           rt->gcUserChunkSet.lookupForAdd(chunk);
+    GCChunkSet::AddPtr p = chunkSet->lookupForAdd(chunk);
     JS_ASSERT(!p);
-    if (isSystemCompartment) {
-        if (!rt->gcSystemChunkSet.add(p, chunk)) {
-            ReleaseGCChunk(rt, chunk);
-            return NULL;
-        }
-    } else {
-        if (!rt->gcUserChunkSet.add(p, chunk)) {
-            ReleaseGCChunk(rt, chunk);
-            return NULL;
-        }
+    if (!chunkSet->add(p, chunk)) {
+        ReleaseGCChunk(rt, chunk);
+        return NULL;
     }
 
-    chunk->init(rt);
     cx->compartment->chunk = chunk;
-    rt->gcChunkAllocationSinceLastGC = true;
     return chunk;
 }
 
 static void
+ReleaseEmptyGCChunks(JSRuntime *rt)
+{
+    for (Chunk *chunk = rt->gcEmptyChunkListHead; chunk; ) {
+        Chunk *next = chunk->info.link;
+        ReleaseGCChunk(rt, chunk);
+        chunk = next;
+    }
+    rt->gcEmptyChunkListHead = NULL;
+    rt->gcEmptyChunkCount = 0;
+}
+
+static void
 ExpireGCChunks(JSRuntime *rt, JSGCInvocationKind gckind)
 {
-    static const size_t MaxAge = 3;
-
-    /* Remove unused chunks. */
     AutoLockGC lock(rt);
 
-    rt->gcChunksWaitingToExpire = 0;
-    for (GCChunkSet::Enum e(rt->gcUserChunkSet); !e.empty(); e.popFront()) {
-        Chunk *chunk = e.front();
-        JS_ASSERT(chunk->info.runtime == rt);
-        if (chunk->unused()) {
-            if (gckind == GC_SHRINK || chunk->info.age++ > MaxAge) {
-                e.removeFront();
+    if (gckind == GC_SHRINK) {
+        ReleaseEmptyGCChunks(rt);
+    } else {
+        /* Return old empty chunks to the system. */
+        for (Chunk **chunkp = &rt->gcEmptyChunkListHead; *chunkp; ) {
+            JS_ASSERT(rt->gcEmptyChunkCount);
+            Chunk *chunk = *chunkp;
+            JS_ASSERT(chunk->info.age <= MAX_EMPTY_CHUNK_AGE);
+            if (chunk->info.age == MAX_EMPTY_CHUNK_AGE) {
+                *chunkp = chunk->info.link;
+                --rt->gcEmptyChunkCount;
                 ReleaseGCChunk(rt, chunk);
-                continue;
+            } else {
+                /* Keep the chunk but increase its age. */
+                ++chunk->info.age;
+                chunkp = &chunk->info.link;
             }
-            rt->gcChunksWaitingToExpire++;
-        }
-    }
-    for (GCChunkSet::Enum e(rt->gcSystemChunkSet); !e.empty(); e.popFront()) {
-        Chunk *chunk = e.front();
-        JS_ASSERT(chunk->info.runtime == rt);
-        if (chunk->unused()) {
-            if (gckind == GC_SHRINK || chunk->info.age++ > MaxAge) {
-                e.removeFront();
-                ReleaseGCChunk(rt, chunk);
-                continue;
-            }
-            rt->gcChunksWaitingToExpire++;
         }
     }
 }
 
 JS_FRIEND_API(bool)
 IsAboutToBeFinalized(JSContext *cx, const void *thing)
 {
     if (JSAtom::isStatic(thing))
@@ -569,20 +575,20 @@ static const int64 JIT_SCRIPT_EIGHTH_LIF
 
 JSBool
 js_InitGC(JSRuntime *rt, uint32 maxbytes)
 {
     /*
      * Make room for at least 16 chunks so the table would not grow before
      * the browser starts up.
      */
-    if (!rt->gcUserChunkSet.init(16))
+    if (!rt->gcUserChunkSet.init(INITIAL_CHUNK_CAPACITY))
         return false;
 
-    if (!rt->gcSystemChunkSet.init(16))
+    if (!rt->gcSystemChunkSet.init(INITIAL_CHUNK_CAPACITY))
         return false;
 
     if (!rt->gcRootsHash.init(256))
         return false;
 
     if (!rt->gcLocksHash.init(256))
         return false;
 
@@ -928,16 +934,17 @@ js_FinishGC(JSRuntime *rt)
     rt->atomsCompartment = NULL;
 
     for (GCChunkSet::Range r(rt->gcUserChunkSet.all()); !r.empty(); r.popFront())
         ReleaseGCChunk(rt, r.front());
     for (GCChunkSet::Range r(rt->gcSystemChunkSet.all()); !r.empty(); r.popFront())
         ReleaseGCChunk(rt, r.front());
     rt->gcUserChunkSet.clear();
     rt->gcSystemChunkSet.clear();
+    ReleaseEmptyGCChunks(rt);
 
 #ifdef JS_THREADSAFE
     rt->gcHelperThread.finish(rt);
 #endif
 
 #ifdef DEBUG
     if (!rt->gcRootsHash.empty())
         CheckLeakedRoots(rt);
@@ -1083,45 +1090,45 @@ js_MapGCRoots(JSRuntime *rt, JSGCRootMap
     return ct;
 }
 
 void
 JSRuntime::setGCLastBytes(size_t lastBytes, JSGCInvocationKind gckind)
 {
     gcLastBytes = lastBytes;
 
-    size_t base = gckind == GC_SHRINK ? lastBytes : Max(lastBytes, GC_ARENA_ALLOCATION_TRIGGER);
+    size_t base = gckind == GC_SHRINK ? lastBytes : Max(lastBytes, GC_ALLOCATION_THRESHOLD);
     float trigger = float(base) * GC_HEAP_GROWTH_FACTOR;
     gcTriggerBytes = size_t(Min(float(gcMaxBytes), trigger));
 }
 
 void
 JSRuntime::reduceGCTriggerBytes(uint32 amount) {
     JS_ASSERT(amount > 0);
     JS_ASSERT(gcTriggerBytes - amount >= 0);
-    if (gcTriggerBytes - amount < GC_ARENA_ALLOCATION_TRIGGER * GC_HEAP_GROWTH_FACTOR)
+    if (gcTriggerBytes - amount < GC_ALLOCATION_THRESHOLD * GC_HEAP_GROWTH_FACTOR)
         return;
     gcTriggerBytes -= amount;
 }
 
 void
 JSCompartment::setGCLastBytes(size_t lastBytes, JSGCInvocationKind gckind)
 {
     gcLastBytes = lastBytes;
 
-    size_t base = gckind == GC_SHRINK ? lastBytes : Max(lastBytes, GC_ARENA_ALLOCATION_TRIGGER);
+    size_t base = gckind == GC_SHRINK ? lastBytes : Max(lastBytes, GC_ALLOCATION_THRESHOLD);
     float trigger = float(base) * GC_HEAP_GROWTH_FACTOR;
     gcTriggerBytes = size_t(Min(float(rt->gcMaxBytes), trigger));
 }
 
 void
 JSCompartment::reduceGCTriggerBytes(uint32 amount) {
     JS_ASSERT(amount > 0);
     JS_ASSERT(gcTriggerBytes - amount >= 0);
-    if (gcTriggerBytes - amount < GC_ARENA_ALLOCATION_TRIGGER * GC_HEAP_GROWTH_FACTOR)
+    if (gcTriggerBytes - amount < GC_ALLOCATION_THRESHOLD * GC_HEAP_GROWTH_FACTOR)
         return;
     gcTriggerBytes -= amount;
 }
 
 namespace js {
 namespace gc {
 
 inline ArenaHeader *
@@ -1927,17 +1934,17 @@ MaybeGC(JSContext *cx)
     }
 
     /*
      * On 32 bit setting gcNextFullGCTime below is not atomic and a race condition
      * could trigger an GC. We tolerate this.
      */
     int64 now = PRMJ_Now();
     if (rt->gcNextFullGCTime && rt->gcNextFullGCTime <= now) {
-        if (rt->gcChunkAllocationSinceLastGC || rt->gcChunksWaitingToExpire) {
+        if (rt->gcChunkAllocationSinceLastGC || rt->gcEmptyChunkListHead) {
             GCREASON(MAYBEGC);
             js_GC(cx, NULL, GC_SHRINK);
         } else {
             rt->gcNextFullGCTime = now + GC_IDLE_FULL_SPAN;
         }
     }
 }
 
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -608,16 +608,22 @@ struct Chunk {
         return offset < ArenasPerChunk * ArenaSize;
     }
 
     static size_t arenaIndex(uintptr_t addr) {
         JS_ASSERT(withinArenasRange(addr));
         return (addr & GC_CHUNK_MASK) >> ArenaShift;
     }
 
+    uintptr_t address() const {
+        uintptr_t addr = reinterpret_cast<uintptr_t>(this);
+        JS_ASSERT(!(addr & GC_CHUNK_MASK));
+        return addr;
+    }
+
     void init(JSRuntime *rt);
     bool unused();
     bool hasAvailableArenas();
     bool withinArenasRange(Cell *cell);
 
     template <size_t thingSize>
     ArenaHeader *allocateArena(JSContext *cx, unsigned thingKind);
 
@@ -745,23 +751,22 @@ Cell::compartment() const
 /*
  * One past the maximum trace kind.
  */
 #define JSTRACE_LIMIT       4
 
 /*
  * Lower limit after which we limit the heap growth
  */
-const size_t GC_ARENA_ALLOCATION_TRIGGER = 30 * js::GC_CHUNK_SIZE;
+const size_t GC_ALLOCATION_THRESHOLD = 30 * 1024 * 1024;
 
 /*
- * A GC is triggered once the number of newly allocated arenas
- * is GC_HEAP_GROWTH_FACTOR times the number of live arenas after
- * the last GC starting after the lower limit of
- * GC_ARENA_ALLOCATION_TRIGGER.
+ * A GC is triggered once the number of newly allocated arenas is
+ * GC_HEAP_GROWTH_FACTOR times the number of live arenas after the last GC
+ * starting after the lower limit of GC_ALLOCATION_THRESHOLD.
  */
 const float GC_HEAP_GROWTH_FACTOR = 3.0f;
 
 /* Perform a Full GC every 20 seconds if MaybeGC is called */
 static const int64 GC_IDLE_FULL_SPAN = 20 * 1000 * 1000;
 
 static inline size_t
 GetFinalizableTraceKind(size_t thingKind)
@@ -993,19 +998,27 @@ struct FreeLists {
             JS_ASSERT(lists[i].isEmpty());
 #endif
     }
 };
 
 extern void *
 RefillFinalizableFreeList(JSContext *cx, unsigned thingKind);
 
-} /* namespace gc */
+/*
+ * Initial allocation size for data structures holding chunks is set to hold
+ * chunks with total capacity of 16MB to avoid buffer resizes during browser
+ * startup.
+ */
+const size_t INITIAL_CHUNK_CAPACITY = 16 * 1024 * 1024 / GC_CHUNK_SIZE;
 
-typedef Vector<gc::Chunk *, 32, SystemAllocPolicy> GCChunks;
+/* The number of GC cycles an empty chunk can survive before been released. */
+const size_t MAX_EMPTY_CHUNK_AGE = 4;
+
+} /* namespace gc */
 
 struct GCPtrHasher
 {
     typedef void *Lookup;
 
     static HashNumber hash(void *key) {
         return HashNumber(uintptr_t(key) >> JS_GCTHING_ZEROBITS);
     }