Bug 1436247 - Part 1. Spawn image decoder threads on demand, rather than at startup. r=tnikkel
☠☠ backed out by f1e39e547b29 ☠ ☠
authorAndrew Osmond <aosmond@mozilla.com>
Tue, 13 Feb 2018 06:43:30 -0500
changeset 403679 c32ead4e35257a9067fa2fa73a96fafca4acdacd
parent 403678 e7519d21db96d2e0e48b84668ed8ac47b1e59b9a
child 403680 3650631487c779979d0b2c693864617d5351c516
push id33443
push userdluca@mozilla.com
push dateWed, 14 Feb 2018 22:23:22 +0000
treeherdermozilla-central@27fd083ed7ee [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1436247
milestone60.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 1436247 - Part 1. Spawn image decoder threads on demand, rather than at startup. r=tnikkel Currently imagelib's DecodePool spawns the maximum number of threads during startup, based on the number of processors. This patch changes it to spawn a single thread on startup (which cannot fail), and more up to the maximum as jobs are added to the queue. A thread will only be spawned if there is a backlog present when a new job is added. This typically results in fewer threads allocated in the parent process, as well as deferred spawning in the content processes.
image/DecodePool.cpp
image/DecodePool.h
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -50,40 +50,57 @@ struct Work
 };
 
 class DecodePoolImpl
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
 
-  DecodePoolImpl()
+  DecodePoolImpl(uint8_t aMaxThreads)
     : mMonitor("DecodePoolImpl")
+    , mThreads(aMaxThreads)
+    , mAvailableThreads(aMaxThreads)
+    , mIdleThreads(0)
     , mShuttingDown(false)
-  { }
+  {
+    MonitorAutoLock lock(mMonitor);
+    bool success = CreateThread();
+    MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
+  }
 
   /// Shut down the provided decode pool thread.
   static void ShutdownThread(nsIThread* aThisThread)
   {
     // Threads have to be shut down from another thread, so we'll ask the
     // main thread to do it for us.
     NS_DispatchToMainThread(NewRunnableMethod("DecodePoolImpl::ShutdownThread",
                                               aThisThread, &nsIThread::Shutdown));
   }
 
   /**
    * Requests shutdown. New work items will be dropped on the floor, and all
    * decode pool threads will be shut down once existing work items have been
    * processed.
    */
-  void RequestShutdown()
+  void Shutdown()
   {
-    MonitorAutoLock lock(mMonitor);
-    mShuttingDown = true;
-    mMonitor.NotifyAll();
+    nsTArray<nsCOMPtr<nsIThread>> threads;
+
+    {
+      MonitorAutoLock lock(mMonitor);
+      mShuttingDown = true;
+      mAvailableThreads = 0;
+      threads.SwapElements(mThreads);
+      mMonitor.NotifyAll();
+    }
+
+    for (uint32_t i = 0 ; i < threads.Length() ; ++i) {
+      threads[i]->Shutdown();
+    }
   }
 
   /// Pushes a new decode work item.
   void PushWork(IDecodingTask* aTask)
   {
     MOZ_ASSERT(aTask);
     RefPtr<IDecodingTask> task(aTask);
 
@@ -95,23 +112,51 @@ public:
     }
 
     if (task->Priority() == TaskPriority::eHigh) {
       mHighPriorityQueue.AppendElement(Move(task));
     } else {
       mLowPriorityQueue.AppendElement(Move(task));
     }
 
+    // If there are pending tasks, create more workers if and only if we have
+    // not exceeded the capacity, and any previously created workers are ready.
+    if (mAvailableThreads) {
+      size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
+      if (pending > mIdleThreads) {
+        CreateThread();
+      }
+    }
+
     mMonitor.Notify();
   }
 
-  /// Pops a new work item, blocking if necessary.
+  Work StartWork()
+  {
+    MonitorAutoLock lock(mMonitor);
+
+    // The thread was already marked as idle when it was created. Once it gets
+    // its first work item, it is assumed it is busy performing that work until
+    // it blocks on the monitor once again.
+    MOZ_ASSERT(mIdleThreads > 0);
+    --mIdleThreads;
+    return PopWorkLocked();
+  }
+
   Work PopWork()
   {
     MonitorAutoLock lock(mMonitor);
+    return PopWorkLocked();
+  }
+
+private:
+  /// Pops a new work item, blocking if necessary.
+  Work PopWorkLocked()
+  {
+    mMonitor.AssertCurrentThreadOwns();
 
     do {
       if (!mHighPriorityQueue.IsEmpty()) {
         return PopWorkFromQueue(mHighPriorityQueue);
       }
 
       if (!mLowPriorityQueue.IsEmpty()) {
         return PopWorkFromQueue(mLowPriorityQueue);
@@ -119,88 +164,114 @@ public:
 
       if (mShuttingDown) {
         Work work;
         work.mType = Work::Type::SHUTDOWN;
         return work;
       }
 
       // Nothing to do; block until some work is available.
+      ++mIdleThreads;
+      MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
       mMonitor.Wait();
+      MOZ_ASSERT(mIdleThreads > 0);
+      --mIdleThreads;
     } while (true);
   }
 
-  nsresult CreateThread(nsIThread** aThread, nsIRunnable* aInitialEvent)
-  {
-    return NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
-                             aThread, aInitialEvent);
-  }
+  ~DecodePoolImpl() { }
 
-private:
-  ~DecodePoolImpl() { }
+  bool CreateThread();
 
   Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue)
   {
     Work work;
     work.mType = Work::Type::TASK;
     work.mTask = aQueue.LastElement().forget();
     aQueue.RemoveElementAt(aQueue.Length() - 1);
 
     return work;
   }
 
   nsThreadPoolNaming mThreadNaming;
 
-  // mMonitor guards the queues and mShuttingDown.
+  // mMonitor guards everything below.
   Monitor mMonitor;
   nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
   nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
+  nsTArray<nsCOMPtr<nsIThread>> mThreads;
+  uint8_t mAvailableThreads; // How many new threads can be created.
+  uint8_t mIdleThreads; // How many created threads are waiting.
   bool mShuttingDown;
 };
 
-class DecodePoolWorker : public Runnable
+class DecodePoolWorker final : public Runnable
 {
 public:
   explicit DecodePoolWorker(DecodePoolImpl* aImpl)
     : Runnable("image::DecodePoolWorker")
     , mImpl(aImpl)
   { }
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
     nsCOMPtr<nsIThread> thisThread;
     nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
 
+    Work work = mImpl->StartWork();
     do {
-      Work work = mImpl->PopWork();
       switch (work.mType) {
         case Work::Type::TASK:
           work.mTask->Run();
+          work.mTask = nullptr;
           break;
 
         case Work::Type::SHUTDOWN:
           DecodePoolImpl::ShutdownThread(thisThread);
           PROFILER_UNREGISTER_THREAD();
           return NS_OK;
 
         default:
           MOZ_ASSERT_UNREACHABLE("Unknown work type");
       }
+
+      work = mImpl->PopWork();
     } while (true);
 
     MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
     return NS_OK;
   }
 
 private:
   RefPtr<DecodePoolImpl> mImpl;
 };
 
+bool DecodePoolImpl::CreateThread()
+{
+  mMonitor.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mAvailableThreads > 0);
+
+  nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this);
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
+                                  getter_AddRefs(thread), worker);
+  if (NS_FAILED(rv) || !thread) {
+    MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
+    return false;
+  }
+
+  mThreads.AppendElement(Move(thread));
+  --mAvailableThreads;
+  ++mIdleThreads;
+  MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
+  return true;
+}
+
 /* static */ void
 DecodePool::Initialize()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
   DecodePool::Singleton();
 }
 
@@ -218,18 +289,17 @@ DecodePool::Singleton()
 
 /* static */ uint32_t
 DecodePool::NumberOfCores()
 {
   return sNumCores;
 }
 
 DecodePool::DecodePool()
-  : mImpl(new DecodePoolImpl)
-  , mMutex("image::DecodePool")
+  : mMutex("image::DecodePool")
 {
   // Determine the number of threads we want.
   int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
   uint32_t limit;
   if (prefLimit <= 0) {
     int32_t numCores = NumberOfCores();
     if (numCores <= 1) {
       limit = 1;
@@ -249,24 +319,17 @@ DecodePool::DecodePool()
   }
   // The parent process where there are content processes doesn't need as many
   // threads for decoding images.
   if (limit > 4 && XRE_IsE10sParentProcess()) {
     limit = 4;
   }
 
   // Initialize the thread pool.
-  for (uint32_t i = 0 ; i < limit ; ++i) {
-    nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
-    nsCOMPtr<nsIThread> thread;
-    nsresult rv = mImpl->CreateThread(getter_AddRefs(thread), worker);
-    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread,
-                       "Should successfully create image decoding threads");
-    mThreads.AppendElement(Move(thread));
-  }
+  mImpl = new DecodePoolImpl(limit);
 
   // Initialize the I/O thread.
   nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
                      "Should successfully create image I/O thread");
 
   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
   if (obsSvc) {
@@ -279,30 +342,24 @@ DecodePool::~DecodePool()
   MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
 }
 
 NS_IMETHODIMP
 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
 {
   MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
 
-  nsTArray<nsCOMPtr<nsIThread>> threads;
   nsCOMPtr<nsIThread> ioThread;
 
   {
     MutexAutoLock lock(mMutex);
-    threads.SwapElements(mThreads);
     ioThread.swap(mIOThread);
   }
 
-  mImpl->RequestShutdown();
-
-  for (uint32_t i = 0 ; i < threads.Length() ; ++i) {
-    threads[i]->Shutdown();
-  }
+  mImpl->Shutdown();
 
   if (ioThread) {
     ioThread->Shutdown();
   }
 
   return NS_OK;
 }
 
--- a/image/DecodePool.h
+++ b/image/DecodePool.h
@@ -34,17 +34,17 @@ class IDecodingTask;
  * owns a pool of image decoding threads that are used for asynchronous
  * decoding.
  *
  * DecodePool allows callers to run a decoder, handling management of the
  * decoder's lifecycle and whether it executes on the main thread,
  * off-main-thread in the image decoding thread pool, or on some combination of
  * the two.
  */
-class DecodePool : public nsIObserver
+class DecodePool final : public nsIObserver
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   /// Initializes the singleton instance. Should be called from the main thread.
   static void Initialize();
 
@@ -90,18 +90,17 @@ private:
   DecodePool();
   virtual ~DecodePool();
 
   static StaticRefPtr<DecodePool> sSingleton;
   static uint32_t sNumCores;
 
   RefPtr<DecodePoolImpl> mImpl;
 
-  // mMutex protects mThreads and mIOThread.
+  // mMutex protects mIOThread.
   Mutex                         mMutex;
-  nsTArray<nsCOMPtr<nsIThread>> mThreads;
   nsCOMPtr<nsIThread>           mIOThread;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_DecodePool_h