Bug 716014 Investigate if we could use CompartmentGC more often, r=billm+terrence

--HG--
extra : rebase_source : f0cfb9cdd2e2823898f4c18402df53e7b6041bac
This commit is contained in:
Olli Pettay 2012-05-09 21:53:23 +03:00
parent 2ec243c586
commit 5cd5cd6fc2
7 changed files with 127 additions and 27 deletions

View File

@ -1012,7 +1012,7 @@ nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener *aListener,
} }
#endif #endif
nsJSContext::GarbageCollectNow(js::gcreason::DOM_UTILS); nsJSContext::GarbageCollectNow(js::gcreason::DOM_UTILS, nsGCNormal, true);
nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls); nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls);
return NS_OK; return NS_OK;

View File

@ -134,6 +134,10 @@ static PRLogModuleInfo* gJSDiagnostics;
// doing the first GC. // doing the first GC.
#define NS_FIRST_GC_DELAY 10000 // ms #define NS_FIRST_GC_DELAY 10000 // ms
#define NS_FULL_GC_DELAY 60000 // ms
#define NS_MAX_COMPARTMENT_GC_COUNT 20
// Maximum amount of time that should elapse between incremental GC slices // Maximum amount of time that should elapse between incremental GC slices
#define NS_INTERSLICE_GC_DELAY 100 // ms #define NS_INTERSLICE_GC_DELAY 100 // ms
@ -159,6 +163,7 @@ static PRLogModuleInfo* gJSDiagnostics;
static nsITimer *sGCTimer; static nsITimer *sGCTimer;
static nsITimer *sShrinkGCBuffersTimer; static nsITimer *sShrinkGCBuffersTimer;
static nsITimer *sCCTimer; static nsITimer *sCCTimer;
static nsITimer *sFullGCTimer;
static PRTime sLastCCEndTime; static PRTime sLastCCEndTime;
@ -178,6 +183,7 @@ static bool sLoadingInProgress;
static PRUint32 sCCollectedWaitingForGC; static PRUint32 sCCollectedWaitingForGC;
static bool sPostGCEventsToConsole; static bool sPostGCEventsToConsole;
static bool sDisableExplicitCompartmentGC;
static PRUint32 sCCTimerFireCount = 0; static PRUint32 sCCTimerFireCount = 0;
static PRUint32 sMinForgetSkippableTime = PR_UINT32_MAX; static PRUint32 sMinForgetSkippableTime = PR_UINT32_MAX;
static PRUint32 sMaxForgetSkippableTime = 0; static PRUint32 sMaxForgetSkippableTime = 0;
@ -185,9 +191,10 @@ static PRUint32 sTotalForgetSkippableTime = 0;
static PRUint32 sRemovedPurples = 0; static PRUint32 sRemovedPurples = 0;
static PRUint32 sForgetSkippableBeforeCC = 0; static PRUint32 sForgetSkippableBeforeCC = 0;
static PRUint32 sPreviousSuspectedCount = 0; static PRUint32 sPreviousSuspectedCount = 0;
static PRUint32 sCompartmentGCCount = NS_MAX_COMPARTMENT_GC_COUNT;
static PRUint32 sCleanupsSinceLastGC = PR_UINT32_MAX; static PRUint32 sCleanupsSinceLastGC = PR_UINT32_MAX;
static bool sNeedsFullCC = false; static bool sNeedsFullCC = false;
static nsJSContext *sContextList = nsnull;
nsScriptNameSpaceManager *gNameSpaceManager; nsScriptNameSpaceManager *gNameSpaceManager;
@ -229,7 +236,8 @@ nsMemoryPressureObserver::Observe(nsISupports* aSubject, const char* aTopic,
const PRUnichar* aData) const PRUnichar* aData)
{ {
if (sGCOnMemoryPressure) { if (sGCOnMemoryPressure) {
nsJSContext::GarbageCollectNow(js::gcreason::MEM_PRESSURE, nsGCShrinking); nsJSContext::GarbageCollectNow(js::gcreason::MEM_PRESSURE, nsGCShrinking,
true);
nsJSContext::CycleCollectNow(); nsJSContext::CycleCollectNow();
} }
return NS_OK; return NS_OK;
@ -929,6 +937,8 @@ static const char js_pccounts_content_str[] = JS_OPTIONS_DOT_STR "pccounts.con
static const char js_pccounts_chrome_str[] = JS_OPTIONS_DOT_STR "pccounts.chrome"; static const char js_pccounts_chrome_str[] = JS_OPTIONS_DOT_STR "pccounts.chrome";
static const char js_jit_hardening_str[] = JS_OPTIONS_DOT_STR "jit_hardening"; static const char js_jit_hardening_str[] = JS_OPTIONS_DOT_STR "jit_hardening";
static const char js_memlog_option_str[] = JS_OPTIONS_DOT_STR "mem.log"; static const char js_memlog_option_str[] = JS_OPTIONS_DOT_STR "mem.log";
static const char js_disable_explicit_compartment_gc[] =
JS_OPTIONS_DOT_STR "disable_explicit_compartment_gc";
int int
nsJSContext::JSOptionChangedCallback(const char *pref, void *data) nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
@ -938,6 +948,8 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
PRUint32 newDefaultJSOptions = oldDefaultJSOptions; PRUint32 newDefaultJSOptions = oldDefaultJSOptions;
sPostGCEventsToConsole = Preferences::GetBool(js_memlog_option_str); sPostGCEventsToConsole = Preferences::GetBool(js_memlog_option_str);
sDisableExplicitCompartmentGC =
Preferences::GetBool(js_disable_explicit_compartment_gc);
bool strict = Preferences::GetBool(js_strict_option_str); bool strict = Preferences::GetBool(js_strict_option_str);
if (strict) if (strict)
@ -1038,9 +1050,16 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
} }
nsJSContext::nsJSContext(JSRuntime *aRuntime) nsJSContext::nsJSContext(JSRuntime *aRuntime)
: mGCOnDestruction(true), : mActive(false),
mGCOnDestruction(true),
mExecuteDepth(0) mExecuteDepth(0)
{ {
mNext = sContextList;
mPrev = &sContextList;
if (sContextList) {
sContextList->mPrev = &mNext;
}
sContextList = this;
++sContextCount; ++sContextCount;
@ -1079,6 +1098,11 @@ nsJSContext::~nsJSContext()
nsCycleCollector_DEBUG_wasFreed(static_cast<nsIScriptContext*>(this)); nsCycleCollector_DEBUG_wasFreed(static_cast<nsIScriptContext*>(this));
#endif #endif
*mPrev = mNext;
if (mNext) {
mNext->mPrev = mPrev;
}
// We may still have pending termination functions if the context is destroyed // We may still have pending termination functions if the context is destroyed
// before they could be executed. In this case, free the references to their // before they could be executed. In this case, free the references to their
// parameters, but don't execute the functions (see bug 622326). // parameters, but don't execute the functions (see bug 622326).
@ -2849,6 +2873,7 @@ nsJSContext::ScriptEvaluated(bool aTerminated)
if (aTerminated) { if (aTerminated) {
mOperationCallbackTime = 0; mOperationCallbackTime = 0;
mModalStateTime = 0; mModalStateTime = 0;
mActive = true;
} }
} }
@ -2916,9 +2941,20 @@ nsJSContext::ScriptExecuted()
return NS_OK; return NS_OK;
} }
void
FullGCTimerFired(nsITimer* aTimer, void* aClosure)
{
NS_RELEASE(sFullGCTimer);
uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason),
nsGCNormal, true);
}
//static //static
void void
nsJSContext::GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind) nsJSContext::GarbageCollectNow(js::gcreason::Reason aReason, PRUint32 aGckind,
bool aGlobal)
{ {
NS_TIME_FUNCTION_MIN(1.0); NS_TIME_FUNCTION_MIN(1.0);
SAMPLE_LABEL("GC", "GarbageCollectNow"); SAMPLE_LABEL("GC", "GarbageCollectNow");
@ -2935,9 +2971,35 @@ nsJSContext::GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind)
sPendingLoadCount = 0; sPendingLoadCount = 0;
sLoadingInProgress = false; sLoadingInProgress = false;
if (nsContentUtils::XPConnect()) { if (!nsContentUtils::XPConnect()) {
nsContentUtils::XPConnect()->GarbageCollect(reason, gckind); return;
} }
// Use compartment GC when we're not asked to do a shrinking GC nor
// global GC and compartment GC has been called less than
// NS_MAX_COMPARTMENT_GC_COUNT times after the previous global GC.
if (!sDisableExplicitCompartmentGC &&
aGckind != nsGCShrinking && !aGlobal &&
sCompartmentGCCount < NS_MAX_COMPARTMENT_GC_COUNT) {
js::PrepareForFullGC(nsJSRuntime::sRuntime);
for (nsJSContext* cx = sContextList; cx; cx = cx->mNext) {
if (!cx->mActive && cx->mContext) {
if (JSObject* global = cx->GetNativeGlobal()) {
js::SkipCompartmentForGC(js::GetObjectCompartment(global));
}
}
cx->mActive = false;
}
if (js::IsGCScheduled(nsJSRuntime::sRuntime)) {
js::IncrementalGC(nsJSRuntime::sRuntime, aReason);
}
return;
}
for (nsJSContext* cx = sContextList; cx; cx = cx->mNext) {
cx->mActive = false;
}
nsContentUtils::XPConnect()->GarbageCollect(aReason, aGckind);
} }
//static //static
@ -2963,7 +3025,7 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
if (sCCLockedOut) { if (sCCLockedOut) {
// We're in the middle of an incremental GC; finish it first // We're in the middle of an incremental GC; finish it first
nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal); nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal, true);
} }
SAMPLE_LABEL("GC", "CycleCollectNow"); SAMPLE_LABEL("GC", "CycleCollectNow");
@ -3100,7 +3162,8 @@ GCTimerFired(nsITimer *aTimer, void *aClosure)
NS_RELEASE(sGCTimer); NS_RELEASE(sGCTimer);
uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure); uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason), nsGCIncremental); nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason),
nsGCNormal, false);
} }
void void
@ -3156,7 +3219,7 @@ CCTimerFired(nsITimer *aTimer, void *aClosure)
} }
// Finish the current incremental GC // Finish the current incremental GC
nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal); nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal, true);
} }
++sCCTimerFireCount; ++sCCTimerFireCount;
@ -3309,6 +3372,15 @@ nsJSContext::KillGCTimer()
} }
} }
void
nsJSContext::KillFullGCTimer()
{
if (sFullGCTimer) {
sFullGCTimer->Cancel();
NS_RELEASE(sFullGCTimer);
}
}
//static //static
void void
nsJSContext::KillShrinkGCBuffersTimer() nsJSContext::KillShrinkGCBuffersTimer()
@ -3336,6 +3408,7 @@ nsJSContext::KillCCTimer()
void void
nsJSContext::GC(js::gcreason::Reason aReason) nsJSContext::GC(js::gcreason::Reason aReason)
{ {
mActive = true;
PokeGC(aReason); PokeGC(aReason);
} }
@ -3417,20 +3490,23 @@ DOMGCSliceCallback(JSRuntime *aRt, js::GCProgress aProgress, const js::GCDescrip
sCCollectedWaitingForGC = 0; sCCollectedWaitingForGC = 0;
sCleanupsSinceLastGC = 0; sCleanupsSinceLastGC = 0;
if (aDesc.isCompartment) {
// If this is a compartment GC, restart it. We still want
// a full GC to happen. Compartment GCs usually happen as a
// result of last-ditch or MaybeGC. In both cases it is
// probably a time of heavy activity and we want to delay
// the full GC, but we do want it to happen eventually.
nsJSContext::PokeGC(js::gcreason::POST_COMPARTMENT);
}
sNeedsFullCC = true; sNeedsFullCC = true;
nsJSContext::MaybePokeCC(); nsJSContext::MaybePokeCC();
if (!aDesc.isCompartment) { if (aDesc.isCompartment) {
++sCompartmentGCCount;
if (!sFullGCTimer) {
CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer);
js::gcreason::Reason reason = js::gcreason::FULL_GC_TIMER;
sFullGCTimer->InitWithFuncCallback(FullGCTimerFired,
reinterpret_cast<void *>(reason),
NS_FULL_GC_DELAY,
nsITimer::TYPE_ONE_SHOT);
}
} else {
sCompartmentGCCount = 0;
nsJSContext::KillFullGCTimer();
// Avoid shrinking during heavy activity, which is suggested by // Avoid shrinking during heavy activity, which is suggested by
// compartment GC. // compartment GC.
nsJSContext::PokeShrinkGCBuffers(); nsJSContext::PokeShrinkGCBuffers();
@ -3530,7 +3606,7 @@ void
nsJSRuntime::Startup() nsJSRuntime::Startup()
{ {
// initialize all our statics, so that we can restart XPCOM // initialize all our statics, so that we can restart XPCOM
sGCTimer = sCCTimer = nsnull; sGCTimer = sFullGCTimer = sCCTimer = nsnull;
sCCLockedOut = false; sCCLockedOut = false;
sCCLockedOutTime = 0; sCCLockedOutTime = 0;
sLastCCEndTime = 0; sLastCCEndTime = 0;
@ -3538,6 +3614,7 @@ nsJSRuntime::Startup()
sLoadingInProgress = false; sLoadingInProgress = false;
sCCollectedWaitingForGC = 0; sCCollectedWaitingForGC = 0;
sPostGCEventsToConsole = false; sPostGCEventsToConsole = false;
sDisableExplicitCompartmentGC = false;
sNeedsFullCC = false; sNeedsFullCC = false;
gNameSpaceManager = nsnull; gNameSpaceManager = nsnull;
sRuntimeService = nsnull; sRuntimeService = nsnull;
@ -3829,6 +3906,7 @@ nsJSRuntime::Shutdown()
nsJSContext::KillGCTimer(); nsJSContext::KillGCTimer();
nsJSContext::KillShrinkGCBuffersTimer(); nsJSContext::KillShrinkGCBuffersTimer();
nsJSContext::KillCCTimer(); nsJSContext::KillCCTimer();
nsJSContext::KillFullGCTimer();
NS_IF_RELEASE(gNameSpaceManager); NS_IF_RELEASE(gNameSpaceManager);

View File

@ -184,7 +184,9 @@ public:
static void LoadStart(); static void LoadStart();
static void LoadEnd(); static void LoadEnd();
static void GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind = nsGCNormal); static void GarbageCollectNow(js::gcreason::Reason reason,
PRUint32 aGckind,
bool aGlobal);
static void ShrinkGCBuffersNow(); static void ShrinkGCBuffersNow();
// If aExtraForgetSkippableCalls is -1, forgetSkippable won't be // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
// called even if the previous collection was GC. // called even if the previous collection was GC.
@ -199,6 +201,7 @@ public:
static void MaybePokeCC(); static void MaybePokeCC();
static void KillCCTimer(); static void KillCCTimer();
static void KillFullGCTimer();
virtual void GC(js::gcreason::Reason aReason); virtual void GC(js::gcreason::Reason aReason);
@ -238,7 +241,7 @@ private:
nsrefcnt GetCCRefcnt(); nsrefcnt GetCCRefcnt();
JSContext *mContext; JSContext *mContext;
PRUint32 mNumEvaluations; bool mActive;
protected: protected:
struct TerminationFuncHolder; struct TerminationFuncHolder;
@ -307,6 +310,9 @@ private:
PRTime mModalStateTime; PRTime mModalStateTime;
PRUint32 mModalStateDepth; PRUint32 mModalStateDepth;
nsJSContext *mNext;
nsJSContext **mPrev;
// mGlobalObjectRef ensures that the outer window stays alive as long as the // mGlobalObjectRef ensures that the outer window stays alive as long as the
// context does. It is eventually collected by the cycle collector. // context does. It is eventually collected by the cycle collector.
nsCOMPtr<nsIScriptGlobalObject> mGlobalObjectRef; nsCOMPtr<nsIScriptGlobalObject> mGlobalObjectRef;

View File

@ -791,14 +791,14 @@ ContentChild::GetIndexedDBPath()
bool bool
ContentChild::RecvGarbageCollect() ContentChild::RecvGarbageCollect()
{ {
nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC); nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC, nsGCNormal, true);
return true; return true;
} }
bool bool
ContentChild::RecvCycleCollect() ContentChild::RecvCycleCollect()
{ {
nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC); nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC, nsGCNormal, true);
nsJSContext::CycleCollectNow(); nsJSContext::CycleCollectNow();
return true; return true;
} }

View File

@ -172,6 +172,12 @@ struct JSCompartment
gcState = GCScheduled; gcState = GCScheduled;
} }
void unscheduleGC() {
JS_ASSERT(!rt->gcRunning);
JS_ASSERT(gcState != GCRunning);
gcState = NoGCScheduled;
}
bool isGCScheduled() const { bool isGCScheduled() const {
return gcState == GCScheduled; return gcState == GCScheduled;
} }

View File

@ -155,6 +155,12 @@ js::IsGCScheduled(JSRuntime *rt)
return false; return false;
} }
JS_FRIEND_API(void)
js::SkipCompartmentForGC(JSCompartment *comp)
{
comp->unscheduleGC();
}
JS_FRIEND_API(void) JS_FRIEND_API(void)
js::GCForReason(JSRuntime *rt, gcreason::Reason reason) js::GCForReason(JSRuntime *rt, gcreason::Reason reason)
{ {

View File

@ -619,7 +619,8 @@ SizeOfJSContext();
D(DOM_IPC) \ D(DOM_IPC) \
D(DOM_WORKER) \ D(DOM_WORKER) \
D(INTER_SLICE_GC) \ D(INTER_SLICE_GC) \
D(REFRESH_FRAME) D(REFRESH_FRAME) \
D(FULL_GC_TIMER)
namespace gcreason { namespace gcreason {
@ -643,6 +644,9 @@ PrepareForFullGC(JSRuntime *rt);
extern JS_FRIEND_API(bool) extern JS_FRIEND_API(bool)
IsGCScheduled(JSRuntime *rt); IsGCScheduled(JSRuntime *rt);
extern JS_FRIEND_API(void)
SkipCompartmentForGC(JSCompartment *comp);
/* /*
* When triggering a GC using one of the functions below, it is first necessary * When triggering a GC using one of the functions below, it is first necessary
* to select the compartments to be collected. To do this, you can call * to select the compartments to be collected. To do this, you can call