Bug 1013172 - IonMonkey: Implement compilation priority, r=jandem,luke

This commit is contained in:
Hannes Verschore 2014-07-01 06:25:43 +02:00
parent 2be09c48f8
commit b60e363f08
5 changed files with 276 additions and 42 deletions

View File

@ -5672,8 +5672,8 @@ CheckFunctionsParallel(ModuleCompiler &m)
IonSpew(IonSpew_Logs, "Can't log asm.js script. (Compiled on background thread.)");
// Saturate all helper threads plus the main thread.
size_t numParallelJobs = HelperThreadState().threadCount + 1;
// Saturate all helper threads.
size_t numParallelJobs = HelperThreadState().maxAsmJSCompilationThreads();
// Allocate scoped AsmJSParallelTask objects. Each contains a unique
// LifoAlloc that provides all necessary memory for compilation.

View File

@ -81,12 +81,21 @@ class MIRGenerator
// Whether the main thread is trying to cancel this build.
bool shouldCancel(const char *why) {
maybePause();
return cancelBuild_;
}
void cancel() {
cancelBuild_ = true;
}
void maybePause() {
if (pauseBuild_ && *pauseBuild_)
PauseCurrentHelperThread();
}
void setPauseFlag(mozilla::Atomic<bool, mozilla::Relaxed> *pauseBuild) {
pauseBuild_ = pauseBuild;
}
void disable() {
abortReason_ = AbortReason_Disable;
}
@ -153,6 +162,7 @@ class MIRGenerator
MIRGraph *graph_;
AbortReason abortReason_;
bool error_;
mozilla::Atomic<bool, mozilla::Relaxed> *pauseBuild_;
mozilla::Atomic<bool, mozilla::Relaxed> cancelBuild_;
uint32_t maxAsmJSStackArgBytes_;

View File

@ -26,6 +26,7 @@ MIRGenerator::MIRGenerator(CompileCompartment *compartment, const JitCompileOpti
graph_(graph),
abortReason_(AbortReason_NoAbort),
error_(false),
pauseBuild_(nullptr),
cancelBuild_(false),
maxAsmJSStackArgBytes_(0),
performsCall_(false),

View File

@ -48,7 +48,10 @@ js::EnsureHelperThreadsInitialized(ExclusiveContext *cx)
static size_t
ThreadCountForCPUCount(size_t cpuCount)
{
return Max(cpuCount, (size_t)2);
// Create additional threads on top of the number of cores available, to
// provide some excess capacity in case threads pause each other.
static const uint32_t EXCESS_THREADS = 4;
return cpuCount + EXCESS_THREADS;
}
void
@ -143,11 +146,15 @@ js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
/* Wait for in progress entries to finish up. */
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
const HelperThread &helper = HelperThreadState().threads[i];
HelperThread &helper = HelperThreadState().threads[i];
while (helper.ionBuilder &&
CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
{
helper.ionBuilder->cancel();
if (helper.pause) {
helper.pause = false;
HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
}
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
}
}
@ -456,6 +463,7 @@ GlobalHelperThreadState::GlobalHelperThreadState()
helperLock = PR_NewLock();
consumerWakeup = PR_NewCondVar(helperLock);
producerWakeup = PR_NewCondVar(helperLock);
pauseWakeup = PR_NewCondVar(helperLock);
}
void
@ -469,6 +477,7 @@ GlobalHelperThreadState::finish()
PR_DestroyCondVar(consumerWakeup);
PR_DestroyCondVar(producerWakeup);
PR_DestroyCondVar(pauseWakeup);
PR_DestroyLock(helperLock);
}
@ -509,7 +518,7 @@ GlobalHelperThreadState::wait(CondVar which, uint32_t millis)
lockOwner = nullptr;
#endif
DebugOnly<PRStatus> status =
PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
PR_WaitCondVar(whichWakeup(which),
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(status == PR_SUCCESS);
#ifdef DEBUG
@ -521,14 +530,14 @@ void
GlobalHelperThreadState::notifyAll(CondVar which)
{
JS_ASSERT(isLocked());
PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
PR_NotifyAllCondVar(whichWakeup(which));
}
void
GlobalHelperThreadState::notifyOne(CondVar which)
{
JS_ASSERT(isLocked());
PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
PR_NotifyCondVar(whichWakeup(which));
}
bool
@ -536,23 +545,135 @@ GlobalHelperThreadState::canStartAsmJSCompile()
{
// Don't execute an AsmJS job if an earlier one failed.
JS_ASSERT(isLocked());
return !asmJSWorklist().empty() && !numAsmJSFailedJobs;
if (asmJSWorklist().empty() || numAsmJSFailedJobs)
return false;
// Honor the maximum allowed threads to compile AsmJS jobs at once,
// to avoid oversaturating the machine.
size_t numAsmJSThreads = 0;
for (size_t i = 0; i < threadCount; i++) {
if (threads[i].asmData)
numAsmJSThreads++;
}
if (numAsmJSThreads >= maxAsmJSCompilationThreads())
return false;
return true;
}
static bool
IonBuilderHasHigherPriority(IonBuilder *first, IonBuilder *second)
{
// This method can return whatever it wants, though it really ought to be a
// total order. The ordering is allowed to race (change on the fly), however.
// A lower optimization level indicates a higher priority.
if (first->optimizationInfo().level() != second->optimizationInfo().level())
return first->optimizationInfo().level() < second->optimizationInfo().level();
// A script without an IonScript has precedence on one with.
if (first->script()->hasIonScript() != second->script()->hasIonScript())
return !first->script()->hasIonScript();
// A higher useCount indicates a higher priority.
return first->script()->getUseCount() > second->script()->getUseCount();
}
bool
GlobalHelperThreadState::canStartIonCompile()
{
// A helper thread can begin an Ion compilation if (a) there is some script
// which is waiting to be compiled, and (b) no other helper thread is
// currently compiling a script. The latter condition ensures that two
// compilations cannot simultaneously occur.
if (ionWorklist().empty())
return false;
for (size_t i = 0; i < threadCount; i++) {
if (threads[i].ionBuilder)
return false;
return !ionWorklist().empty();
}
jit::IonBuilder *
GlobalHelperThreadState::highestPriorityPendingIonCompile(bool remove /* = false */)
{
JS_ASSERT(isLocked());
if (ionWorklist().empty()) {
JS_ASSERT(!remove);
return nullptr;
}
return true;
// Get the highest priority IonBuilder which has not started compilation yet.
size_t index = 0;
for (size_t i = 1; i < ionWorklist().length(); i++) {
if (IonBuilderHasHigherPriority(ionWorklist()[i], ionWorklist()[index]))
index = i;
}
jit::IonBuilder *builder = ionWorklist()[index];
if (remove)
ionWorklist().erase(&ionWorklist()[index]);
return builder;
}
HelperThread *
GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold()
{
JS_ASSERT(isLocked());
// Get the lowest priority IonBuilder which has started compilation and
// isn't paused, unless there are still fewer than the aximum number of
// such builders permitted.
size_t numBuilderThreads = 0;
HelperThread *thread = nullptr;
for (size_t i = 0; i < threadCount; i++) {
if (threads[i].ionBuilder && !threads[i].pause) {
numBuilderThreads++;
if (!thread || IonBuilderHasHigherPriority(thread->ionBuilder, threads[i].ionBuilder))
thread = &threads[i];
}
}
if (numBuilderThreads < maxIonCompilationThreads())
return nullptr;
return thread;
}
HelperThread *
GlobalHelperThreadState::highestPriorityPausedIonCompile()
{
JS_ASSERT(isLocked());
// Get the highest priority IonBuilder which has started compilation but
// which was subsequently paused.
HelperThread *thread = nullptr;
for (size_t i = 0; i < threadCount; i++) {
if (threads[i].pause) {
// Currently, only threads with IonBuilders can be paused.
JS_ASSERT(threads[i].ionBuilder);
if (!thread || IonBuilderHasHigherPriority(threads[i].ionBuilder, thread->ionBuilder))
thread = &threads[i];
}
}
return thread;
}
bool
GlobalHelperThreadState::pendingIonCompileHasSufficientPriority()
{
JS_ASSERT(isLocked());
// Can't compile anything if there are no scripts to compile.
if (!canStartIonCompile())
return false;
// Count the number of threads currently compiling scripts, and look for
// the thread with the lowest priority.
HelperThread *lowestPriorityThread = lowestPriorityUnpausedIonCompileAtThreshold();
// If the number of threads building scripts is less than the maximum, the
// compilation can start immediately.
if (!lowestPriorityThread)
return true;
// If there is a builder in the worklist with higher priority than some
// builder currently being compiled, then that current compilation can be
// paused, so allow the compilation.
if (IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(), lowestPriorityThread->ionBuilder))
return true;
// Compilation will have to wait until one of the active compilations finishes.
return false;
}
bool
@ -815,24 +936,23 @@ HelperThread::handleIonWorkload()
JS_ASSERT(HelperThreadState().canStartIonCompile());
JS_ASSERT(idle());
// Find the ionBuilder with the script having the highest usecount.
GlobalHelperThreadState::IonBuilderVector &ionWorklist = HelperThreadState().ionWorklist();
size_t highest = 0;
for (size_t i = 1; i < ionWorklist.length(); i++) {
if (ionWorklist[i]->script()->getUseCount() >
ionWorklist[highest]->script()->getUseCount())
{
highest = i;
}
}
ionBuilder = ionWorklist[highest];
// Find the IonBuilder in the worklist with the highest priority, and
// remove it from the worklist.
jit::IonBuilder *builder =
HelperThreadState().highestPriorityPendingIonCompile(/* remove = */ true);
// Pop the top IonBuilder and move it to the original place of the
// IonBuilder we took to start compiling. If both are the same, only pop.
if (highest != ionWorklist.length() - 1)
ionWorklist[highest] = ionWorklist.popCopy();
else
ionWorklist.popBack();
// If there are now too many threads with active IonBuilders, indicate to
// the one with the lowest priority that it should pause. Note that due to
// builder priorities changing since pendingIonCompileHasSufficientPriority
// was called, the builder we are pausing may actually be higher priority
// than the one we are about to start. Oh well.
if (HelperThread *other = HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold()) {
JS_ASSERT(other->ionBuilder && !other->pause);
other->pause = true;
}
ionBuilder = builder;
ionBuilder->setPauseFlag(&pause);
TraceLogger *logger = TraceLoggerForCurrentThread();
AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script()));
@ -852,6 +972,7 @@ HelperThread::handleIonWorkload()
FinishOffThreadIonCompile(ionBuilder);
ionBuilder = nullptr;
pause = false;
// Ping the main thread so that the compiled code can be incorporated
// at the next interrupt callback. Don't interrupt Ion code for this, as
@ -861,11 +982,59 @@ HelperThread::handleIonWorkload()
// Notify the main thread in case it is waiting for the compilation to finish.
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
// When finishing Ion compilation jobs, we can start unpausing compilation
// threads that were paused to restrict the number of active compilations.
// Only unpause one at a time, to make sure we don't exceed the restriction.
// Since threads are currently only paused for Ion compilations, this
// strategy will eventually unpause all paused threads, regardless of how
// many there are, since each thread we unpause will eventually finish and
// end up back here.
if (HelperThread *other = HelperThreadState().highestPriorityPausedIonCompile()) {
JS_ASSERT(other->ionBuilder && other->pause);
// Only unpause the other thread if there isn't a higher priority
// builder which this thread or another can start on.
jit::IonBuilder *builder = HelperThreadState().highestPriorityPendingIonCompile();
if (!builder || IonBuilderHasHigherPriority(other->ionBuilder, builder)) {
other->pause = false;
// Notify all paused threads, to make sure the one we just
// unpaused wakes up.
HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
}
}
#else
MOZ_CRASH();
#endif // JS_ION
}
static HelperThread *
CurrentHelperThread()
{
PRThread *prThread = PR_GetCurrentThread();
HelperThread *thread = nullptr;
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
if (prThread == HelperThreadState().threads[i].thread) {
thread = &HelperThreadState().threads[i];
break;
}
}
JS_ASSERT(thread);
return thread;
}
void
js::PauseCurrentHelperThread()
{
HelperThread *thread = CurrentHelperThread();
AutoLockHelperThreadState lock;
while (thread->pause)
HelperThreadState().wait(GlobalHelperThreadState::PAUSE);
}
void
ExclusiveContext::setHelperThread(HelperThread *thread)
{
@ -1070,14 +1239,17 @@ HelperThread::threadLoop()
threadData.ref().nativeStackLimit[i] = stackLimit;
while (true) {
JS_ASSERT(!ionBuilder && !asmData);
JS_ASSERT(idle());
// Block until a task is available.
// Block until a task is available. Save the value of whether we are
// going to do an Ion compile, in case the value returned by the method
// changes.
bool ionCompile = false;
while (true) {
if (terminate)
return;
if (HelperThreadState().canStartIonCompile() ||
HelperThreadState().canStartAsmJSCompile() ||
if (HelperThreadState().canStartAsmJSCompile() ||
(ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority()) ||
HelperThreadState().canStartParseTask() ||
HelperThreadState().canStartCompressionTask() ||
HelperThreadState().canStartGCHelperTask())
@ -1090,7 +1262,7 @@ HelperThread::threadLoop()
// Dispatch tasks, prioritizing AsmJS work.
if (HelperThreadState().canStartAsmJSCompile())
handleAsmJSWorkload();
else if (HelperThreadState().canStartIonCompile())
else if (ionCompile)
handleIonWorkload();
else if (HelperThreadState().canStartParseTask())
handleParseWorkload();
@ -1166,4 +1338,10 @@ ExclusiveContext::addPendingOverRecursed()
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
}
void
js::PauseCurrentHelperThread()
{
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
}
#endif /* JS_THREADSAFE */

View File

@ -86,6 +86,15 @@ class GlobalHelperThreadState
GCHelperStateVector gcHelperWorklist_;
public:
size_t maxIonCompilationThreads() const {
return 1;
}
size_t maxAsmJSCompilationThreads() const {
if (cpuCount < 2)
return 2;
return cpuCount;
}
GlobalHelperThreadState();
void ensureInitialized();
@ -103,7 +112,11 @@ class GlobalHelperThreadState
CONSUMER,
// For notifying threads doing work that they may be able to make progress.
PRODUCER
PRODUCER,
// For notifying threads doing work which are paused that they may be
// able to resume making progress.
PAUSE
};
void wait(CondVar which, uint32_t timeoutMillis = 0);
@ -165,6 +178,14 @@ class GlobalHelperThreadState
bool canStartCompressionTask();
bool canStartGCHelperTask();
// Unlike the methods above, the value returned by this method can change
// over time, even if the helper thread state lock is held throughout.
bool pendingIonCompileHasSufficientPriority();
jit::IonBuilder *highestPriorityPendingIonCompile(bool remove = false);
HelperThread *lowestPriorityUnpausedIonCompileAtThreshold();
HelperThread *highestPriorityPausedIonCompile();
uint32_t harvestFailedAsmJSJobs() {
JS_ASSERT(isLocked());
uint32_t n = numAsmJSFailedJobs;
@ -207,6 +228,16 @@ class GlobalHelperThreadState
/* Condvars for threads waiting/notifying each other. */
PRCondVar *consumerWakeup;
PRCondVar *producerWakeup;
PRCondVar *pauseWakeup;
PRCondVar *whichWakeup(CondVar which) {
switch (which) {
case CONSUMER: return consumerWakeup;
case PRODUCER: return producerWakeup;
case PAUSE: return pauseWakeup;
default: MOZ_CRASH();
}
}
/*
* Number of AsmJS jobs that encountered failure for the active module.
@ -234,9 +265,19 @@ struct HelperThread
mozilla::Maybe<PerThreadData> threadData;
PRThread *thread;
/* Indicate to an idle thread that it should finish executing. */
/*
* Indicate to a thread that it should terminate itself. This is only read
* or written with the helper thread state lock held.
*/
bool terminate;
/*
* Indicate to a thread that it should pause execution. This is only
* written with the helper thread state lock held, but may be read from
* without the lock held.
*/
mozilla::Atomic<bool, mozilla::Relaxed> pause;
/* Any builder currently being compiled by Ion on this thread. */
jit::IonBuilder *ionBuilder;
@ -281,6 +322,10 @@ EnsureHelperThreadsInitialized(ExclusiveContext *cx);
void
SetFakeCPUCount(size_t count);
// Pause the current thread until it's pause flag is unset.
void
PauseCurrentHelperThread();
#ifdef JS_ION
/* Perform MIR optimization and LIR generation on a single function. */