Bug 888562 - Recycle MemoryImage surfaces when possible. r=mattwoodrow,jrmuizel
☠☠ backed out by f8257d93273a ☠ ☠
authorAndreas Gal <gal@uci.edu>, Matt Woodrow <mwoodrow@mozilla.com>
Tue, 09 Jul 2013 17:52:31 -0400
changeset 138629 b61b7f2e0d0bc41ea160a7960fca5a6741f4ffaa
parent 138628 ef78caaf730110ca1a97d1dc490985f0a7776681
child 138631 6bd848b916c8b466cff70aebad50345820ee91e2
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-esr52@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, jrmuizel
bugs888562
milestone25.0a1
Bug 888562 - Recycle MemoryImage surfaces when possible. r=mattwoodrow,jrmuizel
gfx/layers/ipc/ISurfaceAllocator.cpp
gfx/layers/ipc/ISurfaceAllocator.h
gfx/layers/ipc/ShadowLayerUtilsMac.cpp
gfx/layers/ipc/ShadowLayers.cpp
--- a/gfx/layers/ipc/ISurfaceAllocator.cpp
+++ b/gfx/layers/ipc/ISurfaceAllocator.cpp
@@ -9,26 +9,217 @@
 #include "mozilla/ipc/SharedMemory.h"
 #include "gfxSharedImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxASurface.h"
 #include "mozilla/layers/LayersSurfaces.h"
 #include "mozilla/layers/SharedPlanarYCbCrImage.h"
 #include "mozilla/layers/SharedRGBImage.h"
 #include "nsXULAppAPI.h"
+#include "nsExpirationTracker.h"
+#include "nsContentUtils.h"
 
 #ifdef DEBUG
 #include "prenv.h"
 #endif
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace layers {
 
+// We use MemoryImageData to hold the memory contained in MemoryImage objects. An
+// expiration tracker keeps MemoryImageData objects around for a short period
+// of time to avoid constantly re-allocating memory.
+class MemoryImageData {
+  friend class MemoryImageDataExpirationTracker;
+
+  unsigned char *mData;
+  size_t mSize;
+  nsExpirationState mExpirationState;
+
+  MemoryImageData() :
+    mData(nullptr), mSize(0) { }
+  ~MemoryImageData();
+
+  void Init(size_t aSize) {
+    mData = (unsigned char *)moz_malloc(aSize);
+    mSize = aSize;
+  }
+
+  size_t Size() const {
+    return mSize;
+  }
+
+public:
+  nsExpirationState* GetExpirationState() {
+    return &mExpirationState;
+  }
+
+  unsigned char *Data() const {
+    return mData;
+  }
+  
+  void Clear()
+  {
+    memset(Data(), 0, mSize);
+  }
+
+  static MemoryImageData *Alloc(size_t aSize);
+  static void Destroy(MemoryImageData *);
+};
+
+class MemoryImageDataExpirationTracker MOZ_FINAL :
+  public nsExpirationTracker<MemoryImageData,2>,
+  public nsIObserver {
+  friend class MemoryImageData;
+  friend class DestroyOnMainThread;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+private:
+
+  // With K = 2, this means that memory images will be released when they are not
+  // used for 1-2 seconds.
+  enum { TIMEOUT_MS = 1000 };
+  MemoryImageDataExpirationTracker()
+    : nsExpirationTracker<MemoryImageData,2>(TIMEOUT_MS)
+  {
+    nsContentUtils::RegisterShutdownObserver(this);
+  }
+
+  ~MemoryImageDataExpirationTracker() {
+    AgeAllGenerations();
+  }
+
+  virtual void NotifyExpired(MemoryImageData *aData) {
+    delete aData;
+  }
+
+  static void AddData(MemoryImageData* aData) {
+    NS_ASSERTION(!aData->GetExpirationState()->IsTracked(), "must not be tracked yet");
+    if (!sExpirationTracker) {
+      sExpirationTracker = new MemoryImageDataExpirationTracker();
+    }
+    sExpirationTracker->AddObject(aData);
+  }
+
+  static void RemoveData(MemoryImageData* aData) {
+    NS_ASSERTION(aData->GetExpirationState()->IsTracked(), "must be tracked");
+    if (!sExpirationTracker)
+      return;
+    if (aData->GetExpirationState()->IsTracked()) {
+      sExpirationTracker->RemoveObject(aData);
+    }
+  }
+
+  static MemoryImageData *GetData(size_t aSize) {
+    // Try to reuse a memory image data object already the tracker.
+    if (NS_IsMainThread() && sExpirationTracker) {
+      Iterator i = Iterator(sExpirationTracker);
+      while (MemoryImageData *data = i.Next()) {
+        if (data->Size() >= aSize && aSize > data->Size()/2) {
+          RemoveData(data);
+          return data;
+        }
+      }
+    }
+    return nullptr;
+  }
+
+  // Return a memory image data object to the tracker for reuse until it expires.
+  static void DestroyData(MemoryImageData *aData) {
+    AddData(aData);
+  }
+
+  static void Shutdown()
+  {
+    sExpirationTracker->AgeAllGenerations();
+    sExpirationTracker = nullptr;
+  }
+
+private:
+  static nsRefPtr<MemoryImageDataExpirationTracker> sExpirationTracker;
+  uint32_t sConsumers;
+};
+
+NS_IMPL_ISUPPORTS1(MemoryImageDataExpirationTracker, nsIObserver)
+
+NS_IMETHODIMP
+MemoryImageDataExpirationTracker::Observe(nsISupports *aSubject,
+                                          const char *aTopic,
+                                          const PRUnichar *aData)
+{
+  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+    Shutdown();
+
+    nsContentUtils::UnregisterShutdownObserver(this);
+  }
+  return NS_OK;
+}
+
+nsRefPtr<MemoryImageDataExpirationTracker>
+MemoryImageDataExpirationTracker::sExpirationTracker = nullptr;
+
+MemoryImageData::~MemoryImageData()
+{
+  MemoryImageDataExpirationTracker::RemoveData(this);
+}
+
+MemoryImageData *
+MemoryImageData::Alloc(size_t aSize)
+{
+  // The expiration tracker can only be used from the main thread. This matches our usage
+  // pattern since we allocate buffers on the main thread and send them to the compositor
+  // thread.
+  if (NS_IsMainThread()) {
+    if (MemoryImageData *data = MemoryImageDataExpirationTracker::GetData(aSize))
+      return data;
+  }
+  // No luck, allocate a new one.
+  MemoryImageData *data = new MemoryImageData();
+  data->Init(aSize);
+  return data;
+}
+
+class DestroyOnMainThread : public nsRunnable {
+public:
+  DestroyOnMainThread(MemoryImageData *aData)
+    : mData(aData)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    MemoryImageDataExpirationTracker::DestroyData(mData);
+    return NS_OK;
+  }
+private:
+  MemoryImageData *mData;
+};
+
+void
+MemoryImageData::Destroy(MemoryImageData *aData)
+{
+  // The compositor destroys MemoryImageData objects, so send them back to the main thread
+  // instead of stomping on the non-threadsafe expiration tracker.
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(new DestroyOnMainThread(aData));
+    return;
+  }
+  return MemoryImageDataExpirationTracker::DestroyData(aData);
+}
+
+unsigned char *
+GetMemoryImageData(const MemoryImage &aMemoryImage)
+{
+  return ((MemoryImageData *)aMemoryImage.data())->Data();
+}
+
 SharedMemory::SharedMemoryType OptimalShmemType()
 {
 #if defined(MOZ_PLATFORM_MAEMO) && defined(MOZ_HAVE_SHAREDMEMORYSYSV)
   // Use SysV memory because maemo5 on the N900 only allots 64MB to
   // /dev/shm, even though it has 1GB(!!) of system memory.  Sys V shm
   // is allocated from a different pool.  We don't want an arbitrary
   // cap that's much much lower than available memory on the memory we
   // use for layers.
@@ -85,22 +276,22 @@ ISurfaceAllocator::AllocSurfaceDescripto
       PlatformAllocSurfaceDescriptor(aSize, aContent, aCaps, aBuffer)) {
     return true;
   }
 
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     gfxImageFormat format =
       gfxPlatform::GetPlatform()->OptimalFormatForContent(aContent);
     int32_t stride = gfxASurface::FormatStrideForWidth(format, aSize.width);
-    uint8_t *data = new uint8_t[stride * aSize.height];
+    MemoryImageData *data = MemoryImageData::Alloc(stride * aSize.height);
 #ifdef XP_MACOSX
     // Workaround a bug in Quartz where drawing an a8 surface to another a8
     // surface with OPERATOR_SOURCE still requires the destination to be clear.
     if (format == gfxASurface::ImageFormatA8) {
-      memset(data, 0, stride * aSize.height);
+      data->Clear();
     }
 #endif
     *aBuffer = MemoryImage((uintptr_t)data, aSize, stride, format);
     return true;
   }
 
   nsRefPtr<gfxSharedImageSurface> buffer;
   if (!AllocSharedImageSurface(aSize, aContent,
@@ -135,17 +326,17 @@ ISurfaceAllocator::DestroySharedSurface(
       DeallocShmem(aSurface->get_YCbCrImage().data());
       break;
     case SurfaceDescriptor::TRGBImage:
       DeallocShmem(aSurface->get_RGBImage().data());
       break;
     case SurfaceDescriptor::TSurfaceDescriptorD3D10:
       break;
     case SurfaceDescriptor::TMemoryImage:
-      delete [] (unsigned char *)aSurface->get_MemoryImage().data();
+      MemoryImageData::Destroy(((MemoryImageData *)aSurface->get_MemoryImage().data()));
       break;
     case SurfaceDescriptor::Tnull_t:
     case SurfaceDescriptor::T__None:
       break;
     default:
       NS_RUNTIMEABORT("surface type not implemented!");
   }
   *aSurface = SurfaceDescriptor();
--- a/gfx/layers/ipc/ISurfaceAllocator.h
+++ b/gfx/layers/ipc/ISurfaceAllocator.h
@@ -47,17 +47,19 @@ enum BufferCapabilities {
   MAP_AS_IMAGE_SURFACE = 1 << 0,
   /**
    * The allocated buffer will be used for GL rendering only
    */
   USING_GL_RENDERING_ONLY = 1 << 1
 };
 
 class SurfaceDescriptor;
+class MemoryImage;
 
+unsigned char *GetMemoryImageData(const MemoryImage &);
 
 ipc::SharedMemory::SharedMemoryType OptimalShmemType();
 bool IsSurfaceDescriptorValid(const SurfaceDescriptor& aSurface);
 bool IsSurfaceDescriptorOwned(const SurfaceDescriptor& aDescriptor);
 bool ReleaseOwnedSurfaceDescriptor(const SurfaceDescriptor& aDescriptor);
 /**
  * An interface used to create and destroy surfaces that are shared with the
  * Compositor process (using shmem, or gralloc, or other platform specific memory)
--- a/gfx/layers/ipc/ShadowLayerUtilsMac.cpp
+++ b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp
@@ -35,22 +35,21 @@ ShadowLayerForwarder::PlatformOpenDescri
   if (aSurface.type() == SurfaceDescriptor::TShmem) {
     return gfxSharedQuartzSurface::Open(aSurface.get_Shmem());
   } else if (aSurface.type() == SurfaceDescriptor::TMemoryImage) {
     const MemoryImage& image = aSurface.get_MemoryImage();
     gfxASurface::gfxImageFormat format
       = static_cast<gfxASurface::gfxImageFormat>(image.format());
 
     nsRefPtr<gfxASurface> surf =
-      new gfxQuartzSurface((unsigned char*)image.data(),
+      new gfxQuartzSurface(GetMemoryImageData(image),
                            image.size(),
                            image.stride(),
                            format);
     return surf.forget();
-
   }
   return nullptr;
 }
 
 /*static*/ bool
 ShadowLayerForwarder::PlatformCloseDescriptor(const SurfaceDescriptor& aDescriptor)
 {
   return false;
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -534,17 +534,17 @@ ShadowLayerForwarder::OpenDescriptor(Ope
                                stride,
                                rgbFormat);
     return surf.forget();
   }
   case SurfaceDescriptor::TMemoryImage: {
     const MemoryImage& image = aSurface.get_MemoryImage();
     gfxASurface::gfxImageFormat format
       = static_cast<gfxASurface::gfxImageFormat>(image.format());
-    surf = new gfxImageSurface((unsigned char *)image.data(),
+    surf = new gfxImageSurface(GetMemoryImageData(image),
                                image.size(),
                                image.stride(),
                                format);
     return surf.forget();
   }
   default:
     NS_ERROR("unexpected SurfaceDescriptor type!");
     return nullptr;