Bug 1491037 - Periodically free helper thread LifoAlloc memory r=nbp
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 18 Sep 2018 13:56:45 +0100
changeset 437668 7a9384b6a6c9a54db05468baa4cfb289ab88b6dc
parent 437667 e066ff13872b84231dc343563b47c67da6e20037
child 437669 73019d50638c8dcf9bd489439cd683cea42fc698
push id34690
push usercbrindusan@mozilla.com
push dateFri, 21 Sep 2018 17:30:01 +0000
treeherdermozilla-central@ce4e883f7642 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1491037
milestone64.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 1491037 - Periodically free helper thread LifoAlloc memory r=nbp
js/src/gc/GC.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -4278,16 +4278,21 @@ GCRuntime::purgeRuntime()
     rt->caches().purge();
 
     if (auto cache = rt->maybeThisRuntimeSharedImmutableStrings()) {
         cache->purge();
     }
 
     MOZ_ASSERT(unmarkGrayStack.empty());
     unmarkGrayStack.clearAndFree();
+
+    // If we're the main runtime, tell helper threads to free their unused
+    // memory when they are next idle.
+    if (!rt->parentRuntime)
+        HelperThreadState().triggerFreeUnusedMemory();
 }
 
 bool
 GCRuntime::shouldPreserveJITCode(Realm* realm, const TimeStamp &currentTime,
                                  JS::gcreason::Reason reason, bool canAllocateMoreCode)
 {
     static const auto oneSecond = TimeDuration::FromSeconds(1);
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1232,16 +1232,28 @@ GlobalHelperThreadState::checkTaskThread
     // run.
     if (isMaster && idle == 1) {
         return false;
     }
 
     return true;
 }
 
+void
+GlobalHelperThreadState::triggerFreeUnusedMemory()
+{
+    if (!CanUseExtraThreads())
+        return;
+
+    AutoLockHelperThreadState lock;
+    for (auto& thread : *threads)
+        thread.shouldFreeUnusedMemory = true;
+    notifyAll(PRODUCER, lock);
+}
+
 struct MOZ_RAII AutoSetContextRuntime
 {
     explicit AutoSetContextRuntime(JSRuntime* rt) {
         TlsContext.get()->setRuntime(rt);
     }
     ~AutoSetContextRuntime() {
         TlsContext.get()->setRuntime(nullptr);
     }
@@ -2561,16 +2573,18 @@ HelperThread::threadLoop()
             }
             // Now that we are holding the helper thread state lock again,
             // notify the record/replay system that we might block soon.
             // The helper thread state lock may not be released until the
             // block occurs.
             mozilla::recordreplay::NotifyUnrecordedWait(WakeupAll);
         }
 
+        maybeFreeUnusedMemory(&cx);
+
         // The selectors may depend on the HelperThreadState not changing
         // between task selection and task execution, in particular, on new
         // tasks not being added (because of the lifo structure of the work
         // lists). Unlocking the HelperThreadState between task selection and
         // execution is not well-defined.
 
         const TaskSpec* task = findHighestPriorityTask(lock);
         if (!task) {
@@ -2595,8 +2609,21 @@ HelperThread::findHighestPriorityTask(co
     for (const auto& task : taskSpecs) {
         if ((HelperThreadState().*(task.canStart))(locked)) {
             return &task;
         }
     }
 
     return nullptr;
 }
+
+void
+HelperThread::maybeFreeUnusedMemory(JSContext* cx)
+{
+    MOZ_ASSERT(idle());
+
+    cx->tempLifoAlloc().releaseAll();
+
+    if (shouldFreeUnusedMemory) {
+        cx->tempLifoAlloc().freeAll();
+        shouldFreeUnusedMemory = false;
+    }
+}
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -310,16 +310,18 @@ class GlobalHelperThreadState
 
     bool hasActiveThreads(const AutoLockHelperThreadState&);
     void waitForAllThreads();
     void waitForAllThreadsLocked(AutoLockHelperThreadState&);
 
     template <typename T>
     bool checkTaskThreadLimit(size_t maxThreads, bool isMaster = false) const;
 
+    void triggerFreeUnusedMemory();
+
   private:
     /*
      * Lock protecting all mutable shared state accessed by helper threads, and
      * used by all condition variables.
      */
     js::Mutex helperLock;
 
     /* Condvars for threads waiting/notifying each other. */
@@ -358,16 +360,22 @@ struct HelperThread
     mozilla::Maybe<Thread> thread;
 
     /*
      * Indicate to a thread that it should terminate itself. This is only read
      * or written with the helper thread state lock held.
      */
     bool terminate;
 
+    /*
+     * Indicates that this thread should free its unused memory when it is next
+     * idle.
+     */
+    bool shouldFreeUnusedMemory;
+
     /* The current task being executed by this thread, if any. */
     mozilla::Maybe<HelperTaskUnion> currentTask;
 
     bool idle() const {
         return currentTask.isNothing();
     }
 
     /* Any builder currently being compiled by Ion on this thread. */
@@ -447,16 +455,18 @@ struct HelperThread
     T maybeCurrentTaskAs() {
         if (currentTask.isSome() && currentTask->is<T>()) {
             return currentTask->as<T>();
         }
 
         return nullptr;
     }
 
+    void maybeFreeUnusedMemory(JSContext* cx);
+
     void handleWasmWorkload(AutoLockHelperThreadState& locked, wasm::CompileMode mode);
 
     void handleWasmTier1Workload(AutoLockHelperThreadState& locked);
     void handleWasmTier2Workload(AutoLockHelperThreadState& locked);
     void handleWasmTier2GeneratorWorkload(AutoLockHelperThreadState& locked);
     void handlePromiseHelperTaskWorkload(AutoLockHelperThreadState& locked);
     void handleIonWorkload(AutoLockHelperThreadState& locked);
     void handleIonFreeWorkload(AutoLockHelperThreadState& locked);