From 0a5dc75cb38e4657d0dce0170997871d08edc189 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Tue, 4 Aug 2009 14:58:21 -0700 Subject: [PATCH] Memory-pressure based GC scheduler (506125, r=igor). --- dom/base/nsJSEnvironment.cpp | 20 +-- js/src/configure.in | 2 +- js/src/jsapi.cpp | 82 +---------- js/src/jsapi.h | 21 +-- js/src/jscntxt.cpp | 1 + js/src/jscntxt.h | 48 ++----- js/src/jsgc.cpp | 267 ++++++++++++++++++++--------------- js/src/jsgc.h | 40 ++---- js/src/jsobj.cpp | 6 - js/src/jsscope.cpp | 4 - js/src/jsutil.h | 20 +++ js/src/shell/js.cpp | 11 +- memory/jemalloc/jemalloc.c | 4 + memory/jemalloc/jemalloc.h | 4 + 14 files changed, 225 insertions(+), 305 deletions(-) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 649a7efb70a5..513cf86814f3 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -858,18 +858,22 @@ PrintWinCodebase(nsGlobalWindow *win) } #endif +// Don't GC for the first 10s (startup). +PRIntervalTime startTime = -1; + static void MaybeGC(JSContext *cx) { - size_t bytes = cx->runtime->gcBytes; - size_t lastBytes = cx->runtime->gcLastBytes; - if ((bytes > 8192 && bytes > lastBytes * 16) -#ifdef DEBUG - || cx->runtime->gcZeal > 0 -#endif - ) { - JS_GC(cx); + if (startTime) { + if (startTime == -1) { + startTime = PR_IntervalNow(); + return; + } + if (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) < 10000) + return; + startTime = 0; } + JS_MaybeGC(cx); } static already_AddRefed diff --git a/js/src/configure.in b/js/src/configure.in index f81e5ff00efb..977731b09fce 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -1980,7 +1980,7 @@ case "$target" in DSO_LDOPTS=-SUBSYSTEM:WINDOWS CFLAGS="$CFLAGS -W3 -Gy -Fd\$(COMPILE_PDBFILE)" CXXFLAGS="$CXXFLAGS -W3 -Gy -Fd\$(COMPILE_PDBFILE)" - LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib" + LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib psapi.lib" MOZ_DEBUG_FLAGS='-Zi' MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV' WARNINGS_AS_ERRORS='-WX' diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ccade84c2203..279a890de3dc 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2447,72 +2447,7 @@ JS_GC(JSContext *cx) JS_PUBLIC_API(void) JS_MaybeGC(JSContext *cx) { - JSRuntime *rt; - uint32 bytes, lastBytes; - - rt = cx->runtime; - -#ifdef JS_GC_ZEAL - if (rt->gcZeal > 0) { - JS_GC(cx); - return; - } -#endif - - bytes = rt->gcBytes; - lastBytes = rt->gcLastBytes; - - /* - * We run the GC if we used all available free GC cells and had to - * allocate extra 1/3 of GC arenas since the last run of GC, or if - * we have malloc'd more bytes through JS_malloc than we were told - * to allocate by JS_NewRuntime. - * - * The reason for - * bytes > 4/3 lastBytes - * condition is the following. Bug 312238 changed bytes and lastBytes - * to mean the total amount of memory that the GC uses now and right - * after the last GC. - * - * Before the bug the variables meant the size of allocated GC things - * now and right after the last GC. That size did not include the - * memory taken by free GC cells and the condition was - * bytes > 3/2 lastBytes. - * That is, we run the GC if we have half again as many bytes of - * GC-things as the last time we GC'd. To be compatible we need to - * express that condition through the new meaning of bytes and - * lastBytes. - * - * We write the original condition as - * B*(1-F) > 3/2 Bl*(1-Fl) - * where B is the total memory size allocated by GC and F is the free - * cell density currently and Sl and Fl are the size and the density - * right after GC. The density by definition is memory taken by free - * cells divided by total amount of memory. In other words, B and Bl - * are bytes and lastBytes with the new meaning and B*(1-F) and - * Bl*(1-Fl) are bytes and lastBytes with the original meaning. - * - * Our task is to exclude F and Fl from the last statement. According - * to the stats from bug 331966 comment 23, Fl is about 10-25% for a - * typical run of the browser. It means that the original condition - * implied that we did not run GC unless we exhausted the pool of - * free cells. Indeed if we still have free cells, then B == Bl since - * we did not yet allocated any new arenas and the condition means - * 1 - F > 3/2 (1-Fl) or 3/2Fl > 1/2 + F - * That implies 3/2 Fl > 1/2 or Fl > 1/3. That cannot be fulfilled - * for the state described by the stats. So we can write the original - * condition as: - * F == 0 && B > 3/2 Bl(1-Fl) - * Again using the stats we see that Fl is about 11% when the browser - * starts up and when we are far from hitting rt->gcMaxBytes. With - * this F we have - * F == 0 && B > 3/2 Bl(1-0.11) - * or approximately F == 0 && B > 4/3 Bl. - */ - if ((bytes > 8192 && bytes > lastBytes + lastBytes / 3) || - rt->gcMallocBytes >= rt->gcMaxMallocBytes) { - JS_GC(cx); - } + js_MaybeGC(cx); } JS_PUBLIC_API(JSGCCallback) @@ -2546,17 +2481,10 @@ JS_SetGCParameter(JSRuntime *rt, JSGCParamKey key, uint32 value) case JSGC_MAX_BYTES: rt->gcMaxBytes = value; break; - case JSGC_MAX_MALLOC_BYTES: - rt->gcMaxMallocBytes = value; - break; - case JSGC_STACKPOOL_LIFESPAN: + default: + JS_ASSERT(key == JSGC_STACKPOOL_LIFESPAN); rt->gcEmptyArenaPoolLifespan = value; break; - default: - JS_ASSERT(key == JSGC_TRIGGER_FACTOR); - JS_ASSERT(value >= 100); - rt->setGCTriggerFactor(value); - return; } } @@ -2566,12 +2494,8 @@ JS_GetGCParameter(JSRuntime *rt, JSGCParamKey key) switch (key) { case JSGC_MAX_BYTES: return rt->gcMaxBytes; - case JSGC_MAX_MALLOC_BYTES: - return rt->gcMaxMallocBytes; case JSGC_STACKPOOL_LIFESPAN: return rt->gcEmptyArenaPoolLifespan; - case JSGC_TRIGGER_FACTOR: - return rt->gcTriggerFactor; case JSGC_BYTES: return rt->gcBytes; default: diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 2a6a422331d0..dbc2f043423c 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1259,30 +1259,17 @@ typedef enum JSGCParamKey { /* Maximum nominal heap before last ditch GC. */ JSGC_MAX_BYTES = 0, - /* Number of JS_malloc bytes before last ditch GC. */ - JSGC_MAX_MALLOC_BYTES = 1, - /* Hoard stackPools for this long, in ms, default is 30 seconds. */ - JSGC_STACKPOOL_LIFESPAN = 2, - - /* - * The factor that defines when the GC is invoked. The factor is a - * percent of the memory allocated by the GC after the last run of - * the GC. When the current memory allocated by the GC is more than - * this percent then the GC is invoked. The factor cannot be less - * than 100 since the current memory allocated by the GC cannot be less - * than the memory allocated after the last run of the GC. - */ - JSGC_TRIGGER_FACTOR = 3, + JSGC_STACKPOOL_LIFESPAN = 1, /* Amount of bytes allocated by the GC. */ - JSGC_BYTES = 4, + JSGC_BYTES = 2, /* Number of times when GC was invoked. */ - JSGC_NUMBER = 5, + JSGC_NUMBER = 3, /* Max size of the code cache in bytes. */ - JSGC_MAX_CODE_CACHE_BYTES = 6 + JSGC_MAX_CODE_CACHE_BYTES = 4 } JSGCParamKey; extern JS_PUBLIC_API(void) diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 42b579b363bf..12ed56168e5b 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1779,6 +1779,7 @@ js_TriggerAllOperationCallbacks(JSRuntime *rt, JSBool gcLocked) if (!gcLocked) JS_LOCK_GC(rt); #endif + iter = NULL; while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) JS_TriggerOperationCallback(acx); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index ddfdebe30307..92c6c714183e 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -251,12 +251,6 @@ struct JSThreadData { JSEvalCacheMeter evalCacheMeter; #endif - /* - * Thread-local version of JSRuntime.gcMallocBytes to avoid taking - * locks on each JS_malloc. - */ - size_t gcMallocBytes; - #ifdef JS_THREADSAFE /* * Deallocator task for this thread. @@ -376,16 +370,14 @@ struct JSRuntime { JSDHashTable gcRootsHash; JSDHashTable *gcLocksHash; jsrefcount gcKeepAtoms; - size_t gcBytes; - size_t gcLastBytes; - size_t gcMaxBytes; - size_t gcMaxMallocBytes; uint32 gcEmptyArenaPoolLifespan; uint32 gcLevel; uint32 gcNumber; JSTracer *gcMarkingTracer; - uint32 gcTriggerFactor; - size_t gcTriggerBytes; + size_t gcBytes; + size_t gcMaxBytes; + size_t gcLastRSS; + size_t gcFreed; volatile JSBool gcIsNeeded; /* @@ -414,7 +406,6 @@ struct JSRuntime { #endif JSGCCallback gcCallback; - size_t gcMallocBytes; JSGCArenaInfo *gcUntracedArenaStackTop; #ifdef DEBUG size_t gcTraceLaterCount; @@ -703,9 +694,6 @@ struct JSRuntime { char lastScriptFilename[1024]; #endif - void setGCTriggerFactor(uint32 factor); - void setGCLastBytes(size_t lastBytes); - inline void* malloc(size_t bytes) { return ::js_malloc(bytes); } @@ -1077,15 +1065,15 @@ struct JSContext { #endif #ifdef JS_THREADSAFE - inline void createDeallocatorTask() { - JSThreadData* tls = JS_THREAD_DATA(this); + inline void createDeallocatorTask(size_t *bytesp) { + JSThreadData *tls = JS_THREAD_DATA(this); JS_ASSERT(!tls->deallocatorTask); if (runtime->deallocatorThread && !runtime->deallocatorThread->busy()) - tls->deallocatorTask = new JSFreePointerListTask(); + tls->deallocatorTask = new JSFreePointerListTask(bytesp); } inline void submitDeallocatorTask() { - JSThreadData* tls = JS_THREAD_DATA(this); + JSThreadData *tls = JS_THREAD_DATA(this); if (tls->deallocatorTask) { runtime->deallocatorThread->schedule(tls->deallocatorTask); tls->deallocatorTask = NULL; @@ -1093,46 +1081,32 @@ struct JSContext { } #endif - /* Call this after succesful malloc of memory for GC-related things. */ - inline void updateMallocCounter(size_t nbytes) { - size_t *pbytes, bytes; - - pbytes = &JS_THREAD_DATA(this)->gcMallocBytes; - bytes = *pbytes; - *pbytes = (size_t(-1) - bytes <= nbytes) ? size_t(-1) : bytes + nbytes; - } - - inline void* malloc(size_t bytes) { + inline void *malloc(size_t bytes) { JS_ASSERT(bytes != 0); void *p = runtime->malloc(bytes); if (!p) { JS_ReportOutOfMemory(this); return NULL; } - updateMallocCounter(bytes); return p; } - inline void* calloc(size_t bytes) { + inline void *calloc(size_t bytes) { JS_ASSERT(bytes != 0); void *p = runtime->calloc(bytes); if (!p) { JS_ReportOutOfMemory(this); return NULL; } - updateMallocCounter(bytes); return p; } - inline void* realloc(void* p, size_t bytes) { - void *orig = p; + inline void *realloc(void* p, size_t bytes) { p = runtime->realloc(p, bytes); if (!p) { JS_ReportOutOfMemory(this); return NULL; } - if (!orig) - updateMallocCounter(bytes); return p; } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index d4a31a249df7..660b34436838 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1298,24 +1298,13 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes) } rt->gcLocksHash = NULL; /* create lazily */ - /* - * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes - * for default backward API compatibility. - */ - rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes; + rt->gcMaxBytes = maxbytes; rt->gcEmptyArenaPoolLifespan = 30000; /* - * By default the trigger factor gets maximum possible value. This - * means that GC will not be triggered by growth of GC memory (gcBytes). + * Sample the current resident set size (RSS). */ - rt->setGCTriggerFactor((uint32) -1); - - /* - * The assigned value prevents GC from running when GC memory is too low - * (during JS engine start). - */ - rt->setGCLastBytes(8192); + rt->gcLastRSS = js_GetRSS(); METER(memset(&rt->gcStats, 0, sizeof rt->gcStats)); return JS_TRUE; @@ -1724,42 +1713,6 @@ static struct GCHist { unsigned gchpos = 0; #endif -void -JSRuntime::setGCTriggerFactor(uint32 factor) -{ - JS_ASSERT(factor >= 100); - - gcTriggerFactor = factor; - setGCLastBytes(gcLastBytes); -} - -void -JSRuntime::setGCLastBytes(size_t lastBytes) -{ - gcLastBytes = lastBytes; - uint64 triggerBytes = uint64(lastBytes) * uint64(gcTriggerFactor / 100); - if (triggerBytes != size_t(triggerBytes)) - triggerBytes = size_t(-1); - gcTriggerBytes = size_t(triggerBytes); -} - -static JS_INLINE bool -IsGCThresholdReached(JSRuntime *rt) -{ -#ifdef JS_GC_ZEAL - if (rt->gcZeal >= 1) - return true; -#endif - - /* - * Since the initial value of the gcLastBytes parameter is not equal to - * zero (see the js_InitGC function) the return value is false when - * the gcBytes value is close to zero at the JS engine start. - */ - return rt->gcMallocBytes >= rt->gcMaxMallocBytes || - rt->gcBytes >= rt->gcTriggerBytes; -} - template static JS_INLINE T* NewGCThing(JSContext *cx, uintN flags) { @@ -1776,7 +1729,6 @@ NewGCThing(JSContext *cx, uintN flags) #endif #ifdef JS_THREADSAFE JSBool gcLocked; - uintN localMallocBytes; JSGCThing **lastptr; JSGCThing *tmpthing; uint8 *tmpflagp; @@ -1799,8 +1751,7 @@ NewGCThing(JSContext *cx, uintN flags) JSGCThing *&freeList = cx->thread->gcFreeLists[flindex]; thing = freeList; - localMallocBytes = JS_THREAD_DATA(cx)->gcMallocBytes; - if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) { + if (thing) { flagp = thing->flagp; freeList = thing->next; METER(astats->localalloc++); @@ -1810,14 +1761,6 @@ NewGCThing(JSContext *cx, uintN flags) JS_LOCK_GC(rt); gcLocked = JS_TRUE; - /* Transfer thread-local counter to global one. */ - if (localMallocBytes != 0) { - JS_THREAD_DATA(cx)->gcMallocBytes = 0; - if (rt->gcMaxMallocBytes - rt->gcMallocBytes < localMallocBytes) - rt->gcMallocBytes = rt->gcMaxMallocBytes; - else - rt->gcMallocBytes += localMallocBytes; - } #endif JS_ASSERT(!rt->gcRunning); if (rt->gcRunning) { @@ -1832,7 +1775,7 @@ NewGCThing(JSContext *cx, uintN flags) #endif arenaList = &rt->gcArenaList[flindex]; - doGC = IsGCThresholdReached(rt); + doGC = rt->gcIsNeeded; for (;;) { if (doGC #ifdef JS_TRACER @@ -1861,13 +1804,10 @@ NewGCThing(JSContext *cx, uintN flags) #ifdef JS_THREADSAFE /* * Refill the local free list by taking several things from the - * global free list unless the free list is already populated or - * we are still at rt->gcMaxMallocBytes barrier. The former is - * caused via allocating new things in gcCallback(cx, JSGC_END). - * The latter happens when GC is canceled due to - * gcCallback(cx, JSGC_BEGIN) returning false. + * global free list unless the free list is already populated. + * This is caused by allocating new things in gcCallback(cx, JSGC_END). */ - if (freeList || rt->gcMallocBytes >= rt->gcMaxMallocBytes) + if (freeList) break; tmpthing = arenaList->freeList; @@ -1937,7 +1877,7 @@ testReservedObjects: * arena. Prefer to order free things by ascending address in the * (unscientific) hope of better cache locality. */ - if (freeList || rt->gcMallocBytes >= rt->gcMaxMallocBytes) + if (freeList) break; lastptr = &freeList; maxFreeThings = thingsLimit - arenaList->lastCount; @@ -2064,7 +2004,7 @@ RefillDoubleFreeList(JSContext *cx) return NULL; } - if (IsGCThresholdReached(rt)) + if (rt->gcIsNeeded) goto do_gc; /* @@ -2251,45 +2191,6 @@ js_ReserveObjects(JSContext *cx, size_t nobjects) } #endif -JSBool -js_AddAsGCBytes(JSContext *cx, size_t sz) -{ - JSRuntime *rt; - - rt = cx->runtime; - if (rt->gcBytes >= rt->gcMaxBytes || - sz > (size_t) (rt->gcMaxBytes - rt->gcBytes) || - IsGCThresholdReached(rt)) { - if (JS_ON_TRACE(cx)) { - /* - * If we can't leave the trace, signal OOM condition, otherwise - * exit from trace and proceed with GC. - */ - if (!js_CanLeaveTrace(cx)) { - JS_UNLOCK_GC(rt); - return JS_FALSE; - } - js_LeaveTrace(cx); - } - js_GC(cx, GC_LAST_DITCH); - if (rt->gcBytes >= rt->gcMaxBytes || - sz > (size_t) (rt->gcMaxBytes - rt->gcBytes)) { - JS_UNLOCK_GC(rt); - JS_ReportOutOfMemory(cx); - return JS_FALSE; - } - } - rt->gcBytes += (uint32) sz; - return JS_TRUE; -} - -void -js_RemoveAsGCBytes(JSRuntime *rt, size_t sz) -{ - JS_ASSERT((size_t) rt->gcBytes >= sz); - rt->gcBytes -= (uint32) sz; -} - /* * Shallow GC-things can be locked just by setting the GCF_LOCK bit, because * they have no descendants to mark during the GC. Currently the optimization @@ -3498,9 +3399,6 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) /* Clear gcIsNeeded now, when we are about to start a normal GC cycle. */ rt->gcIsNeeded = JS_FALSE; - /* Reset malloc counter. */ - rt->gcMallocBytes = 0; - #ifdef JS_DUMP_SCOPE_METERS { extern void js_DumpScopeMeters(JSRuntime *rt); js_DumpScopeMeters(rt); @@ -3561,7 +3459,14 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) rt->gcMarkingTracer = NULL; #ifdef JS_THREADSAFE - cx->createDeallocatorTask(); + /* + * Deallocations occur in the background. The background thread will set + * gcFreed once it is done. At the next GC we will substract the amount + * of data we freed in the background from the previous GC cycle's + * RSS sample. + */ + rt->gcFreed = 0; + cx->createDeallocatorTask(&rt->gcFreed); #endif /* @@ -3742,7 +3647,16 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) */ DestroyGCArenas(rt, emptyArenas); + /* Sample Resident Set Size (RSS). */ + rt->gcLastRSS = js_GetRSS(); + #ifdef JS_THREADSAFE + /* + * It is important that we sample rt->gcLastRSS before submitting the + * deallocator task. This way we know that the RSS we sample ddoes not + * contain the bytes we will free in the background, which we will + * account for separately in rt->gcFreed. + */ cx->submitDeallocatorTask(); #endif @@ -3800,7 +3714,6 @@ out: goto restart; } - rt->setGCLastBytes(rt->gcBytes); done_running: rt->gcLevel = 0; rt->gcRunning = rt->gcRegenShapes = false; @@ -3852,3 +3765,129 @@ out: } } } + +void +js_MaybeGC(JSContext *cx) +{ + JSRuntime *rt; + + rt = cx->runtime; + +#ifdef JS_GC_ZEAL + if (rt->gcZeal > 0) { + JS_GC(cx); + return; + } +#endif + + size_t lastRSS = rt->gcLastRSS; + size_t freed = rt->gcFreed; + if (lastRSS > freed) + lastRSS -= freed; + if (js_GetRSS() > (lastRSS + lastRSS/16 + lastRSS/8)) // 18.75% growth? + JS_GC(cx); +} + +#ifdef JS_THREADSAFE +void +JSFreePointerListTask::add(void *ptr) +{ +#ifdef DEBUG + memset(ptr, 0xcd, js_malloc_size(ptr)); +#endif + *(void**)ptr = head; + head = ptr; +} + +void +JSFreePointerListTask::run() +{ + size_t bytes = 0; + void *ptr = head; + while (ptr) { + void* next = *(void **)ptr; + bytes += js_malloc_size(ptr); + js_free(ptr); + ptr = next; + } + JS_ATOMIC_ADD(bytesp, bytes); +} +#endif + +#ifdef __APPLE__ +#include +#include +size_t +js_GetRSS() +{ + task_t task; + task_basic_info ti; + mach_msg_type_number_t count; + + task_for_pid(mach_task_self (), getpid(), &task); + count = TASK_BASIC_INFO_COUNT; + task_info(task, TASK_BASIC_INFO, (task_info_t)&ti, &count); + return ti.resident_size; +} +#endif + +#ifdef WINCE +#include "windows.h" + +size_t +js_GetRSS() +{ + MEMORYSTATUS ms; + GlobalMemoryStatus(&ms); + return ms.dwTotalVirtual - ms.dwAvailVirtual; +} +#else +#ifdef WIN32 +#include "windows.h" +#include "psapi.h" +size_t +js_GetRSS() +{ + PROCESS_MEMORY_COUNTERS pmc; + GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)); + return pmc.WorkingSetSize; +} +#endif +#endif + +#ifdef __linux__ +#include +#include +static const char* +skipFields(const char* p, unsigned n) +{ + while (*++p) + if ((*p == ' ') && (--n == 0)) + return p+1; + return NULL; +} + +static int statfd = 0; + +size_t +js_GetRSS() +{ + char buf[1024]; + if (!statfd) { + sprintf(buf, "/proc/%d/stat", getpid()); + statfd = open(buf, O_RDONLY); + } + if (statfd == -1) + return 0; + lseek(statfd, 0, SEEK_SET); + int n = read(statfd, buf, sizeof(buf)-1); + buf[n] = 0; + const char* p = strrchr(buf, ')') + 2; + if (!p) + return 0; + p = skipFields(p, 20); + if (!p) + return 0; + return atol(p); +} +#endif diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 52ec7982fd68..705ea4c59fe4 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -284,6 +284,9 @@ typedef enum JSGCInvocationKind { extern void js_GC(JSContext *cx, JSGCInvocationKind gckind); +extern void +js_MaybeGC(JSContext *cx); + typedef struct JSGCArenaInfo JSGCArenaInfo; typedef struct JSGCArenaList JSGCArenaList; typedef struct JSGCChunkInfo JSGCChunkInfo; @@ -326,41 +329,15 @@ struct JSWeakRoots { #define JS_CLEAR_WEAK_ROOTS(wr) (memset((wr), 0, sizeof(JSWeakRoots))) -/* - * Increase runtime->gcBytes by sz bytes to account for an allocation outside - * the GC that will be freed only after the GC is run. The function may run - * the last ditch GC to ensure that gcBytes does not exceed gcMaxBytes. It will - * fail if the latter is not possible. - * - * This function requires that runtime->gcLock is held on entry. On successful - * return the lock is still held and on failure it will be released with - * the error reported. - */ -extern JSBool -js_AddAsGCBytes(JSContext *cx, size_t sz); - -extern void -js_RemoveAsGCBytes(JSRuntime* rt, size_t sz); - #ifdef JS_THREADSAFE class JSFreePointerListTask : public JSBackgroundTask { void *head; + size_t *bytesp; public: - JSFreePointerListTask() : head(NULL) {} + JSFreePointerListTask(size_t *bytesp) : head(NULL), bytesp(bytesp) {} - void add(void* ptr) { - *(void**)ptr = head; - head = ptr; - } - - void run() { - void *ptr = head; - while (ptr) { - void *next = *(void **)ptr; - js_free(ptr); - ptr = next; - } - } + void add(void* ptr); + void run(); }; #endif @@ -374,6 +351,9 @@ class JSFreePointerListTask : public JSBackgroundTask { extern void js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx); +extern size_t +js_GetRSS(); + #ifdef DEBUG_notme #define JS_GCMETER 1 #endif diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index bb7ba2608074..e19abc90c683 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -4989,11 +4989,6 @@ js_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, JS_ASSERT(ne->cursor == (jsword) length); if (allocated != 0) { JS_LOCK_GC(cx->runtime); - if (!js_AddAsGCBytes(cx, allocated)) { - /* js_AddAsGCBytes releases the GC lock on failures. */ - cx->free(ne); - return JS_FALSE; - } ne->next = cx->runtime->nativeEnumerators; cx->runtime->nativeEnumerators = ne; JS_ASSERT(((jsuword) ne & (jsuword) 1) == (jsuword) 0); @@ -5082,7 +5077,6 @@ js_TraceNativeEnumerators(JSTracer *trc) js_TraceId(trc, *cursor); } while (++cursor != end); } else if (doGC) { - js_RemoveAsGCBytes(rt, NativeEnumeratorSize(ne->length)); *nep = ne->next; trc->context->free(ne); continue; diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 02791e40cff0..1bfb6aa06c38 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -172,7 +172,6 @@ JSScope::createTable(JSContext *cx, bool report) JS_ReportOutOfMemory(cx); return false; } - cx->updateMallocCounter(JS_BIT(sizeLog2) * sizeof(JSScopeProperty *)); hashShift = JS_DHASH_BITS - sizeLog2; for (sprop = lastProp; sprop; sprop = sprop->parent) { @@ -411,9 +410,6 @@ JSScope::changeTable(JSContext *cx, int change) oldtable = table; table = newtable; - /* Treat the above calloc as a JS_malloc, to match CreateScopeTable. */ - cx->runtime->gcMallocBytes += nbytes; - /* Copy only live entries, leaving removed and free ones behind. */ for (oldspp = oldtable; oldsize != 0; oldspp++) { sprop = SPROP_FETCH(oldspp); diff --git a/js/src/jsutil.h b/js/src/jsutil.h index 6e0895b2fb9d..4512ee43a9ca 100644 --- a/js/src/jsutil.h +++ b/js/src/jsutil.h @@ -202,6 +202,26 @@ static JS_INLINE void js_free(void* p) { free(p); } +#ifdef XP_UNIX +#ifdef __APPLE__ +#include +static JS_INLINE size_t js_malloc_size(void* p) { + return malloc_size(p); +} +#else +#include +static JS_INLINE size_t js_malloc_size(void* p) { + return malloc_usable_size(p); +} +#endif +#endif +#ifdef XP_WIN +#include +static JS_INLINE size_t js_malloc_size(void* p) { + return _msize(p); +} +#endif + JS_END_EXTERN_C #endif /* jsutil_h___ */ diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 3eaecc34bf6a..0ee1cc478ba7 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -308,6 +308,8 @@ GetContextData(JSContext *cx) static JSBool ShellOperationCallback(JSContext *cx) { + JS_MaybeGC(cx); + if (!gCanceled) return JS_TRUE; @@ -1117,16 +1119,12 @@ GCParameter(JSContext *cx, uintN argc, jsval *vp) return JS_FALSE; if (strcmp(paramName, "maxBytes") == 0) { param = JSGC_MAX_BYTES; - } else if (strcmp(paramName, "maxMallocBytes") == 0) { - param = JSGC_MAX_MALLOC_BYTES; } else if (strcmp(paramName, "gcStackpoolLifespan") == 0) { param = JSGC_STACKPOOL_LIFESPAN; } else if (strcmp(paramName, "gcBytes") == 0) { param = JSGC_BYTES; } else if (strcmp(paramName, "gcNumber") == 0) { param = JSGC_NUMBER; - } else if (strcmp(paramName, "gcTriggerFactor") == 0) { - param = JSGC_TRIGGER_FACTOR; } else { JS_ReportError(cx, "the first argument argument must be maxBytes, " @@ -1153,11 +1151,6 @@ GCParameter(JSContext *cx, uintN argc, jsval *vp) "with non-zero value"); return JS_FALSE; } - if (param == JSGC_TRIGGER_FACTOR && value < 100) { - JS_ReportError(cx, - "the gcTriggerFactor value must be >= 100"); - return JS_FALSE; - } JS_SetGCParameter(cx->runtime, param, value); *vp = JSVAL_VOID; return JS_TRUE; diff --git a/memory/jemalloc/jemalloc.c b/memory/jemalloc/jemalloc.c index a3952dee01c4..fd8f799ea07c 100644 --- a/memory/jemalloc/jemalloc.c +++ b/memory/jemalloc/jemalloc.c @@ -6397,7 +6397,11 @@ free(void *ptr) */ size_t +#ifdef __linux__ +malloc_usable_size(void *ptr) +#else malloc_usable_size(const void *ptr) +#endif { #ifdef MALLOC_VALIDATE diff --git a/memory/jemalloc/jemalloc.h b/memory/jemalloc/jemalloc.h index 4419bd0ff56c..0b46ecabf44e 100644 --- a/memory/jemalloc/jemalloc.h +++ b/memory/jemalloc/jemalloc.h @@ -92,7 +92,11 @@ void free(void *ptr); int posix_memalign(void **memptr, size_t alignment, size_t size); void *memalign(size_t alignment, size_t size); +#ifdef __linux +size_t malloc_usable_size(void *ptr); +#else size_t malloc_usable_size(const void *ptr); +#endif void jemalloc_stats(jemalloc_stats_t *stats); /* The x*() functions never return NULL. */