Bug 1060869 (Part 3) - Make the SurfaceCache free only a fraction of its data on memory pressure. r=dholbert
☠☠ backed out by 3c119becc790 ☠ ☠
authorSeth Fowler <seth@mozilla.com>
Tue, 25 Nov 2014 00:10:11 -0800
changeset 241765 a5d577ebb3d04d581b1e8835bfb12f46a274c644
parent 241764 f6989ebe8e1c11730a719d5ee3070bf2cc748333
child 241766 61b0f5391e9db657a7b8e44e04268fb746ed4059
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1060869
milestone36.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 1060869 (Part 3) - Make the SurfaceCache free only a fraction of its data on memory pressure. r=dholbert
gfx/thebes/gfxPrefs.h
image/src/SurfaceCache.cpp
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -234,16 +234,17 @@ private:
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000);
   DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520);
   DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.decodeondraw",                ImageMemDecodeOnDraw, bool, false);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
   DECL_GFX_PREF(Live, "image.mem.hard_limit_decoded_image_kb", ImageMemHardLimitDecodedImageKB, uint32_t, 0);
   DECL_GFX_PREF(Live, "image.mem.max_decoded_image_kb",        ImageMemMaxDecodedImageKB, uint32_t, 50*1024);
   DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield",         ImageMemMaxMSBeforeYield, uint32_t, 400);
+  DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.multithreaded_decoding.enabled",  ImageMTDecodingEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabled, bool, false);
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -235,20 +235,22 @@ private:
  * cache's implementation.
  */
 class SurfaceCacheImpl MOZ_FINAL : public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
 
   SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
+                   uint32_t aSurfaceCacheDiscardFactor,
                    uint32_t aSurfaceCacheSize)
     : mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(),
                          aSurfaceCacheExpirationTimeMS)
     , mMemoryPressureObserver(new MemoryPressureObserver)
+    , mDiscardFactor(aSurfaceCacheDiscardFactor)
     , mMaxCost(aSurfaceCacheSize)
     , mAvailableCost(aSurfaceCacheSize)
     , mLockedCost(0)
   {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os)
       os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
   }
@@ -469,16 +471,42 @@ public:
     // structures are all hash tables. Note that locked surfaces (persistent
     // surfaces belonging to locked images) are not removed, since they aren't
     // present in mCosts.
     while (!mCosts.IsEmpty()) {
       Remove(mCosts.LastElement().GetSurface());
     }
   }
 
+  void DiscardForMemoryPressure()
+  {
+    // Compute our discardable cost. Since locked surfaces aren't discardable,
+    // we exclude them.
+    const Cost discardableCost = (mMaxCost - mAvailableCost) - mLockedCost;
+    MOZ_ASSERT(discardableCost <= mMaxCost, "Discardable cost doesn't add up");
+
+    // Our target is to raise our available cost by (1 / mDiscardFactor) of our
+    // discardable cost - in other words, we want to end up with about
+    // (discardableCost / mDiscardFactor) fewer bytes stored in the surface
+    // cache after we're done.
+    const Cost targetCost = mAvailableCost + (discardableCost / mDiscardFactor);
+
+    if (targetCost > mMaxCost - mLockedCost) {
+      MOZ_ASSERT_UNREACHABLE("Target cost is more than we can discard");
+      DiscardAll();
+      return;
+    }
+
+    // Discard surfaces until we've reduced our cost to our target cost.
+    while (mAvailableCost < targetCost) {
+      MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and still not done");
+      Remove(mCosts.LastElement().GetSurface());
+    }
+  }
+
   static PLDHashOperator DoStopTracking(const SurfaceKey&,
                                         CachedSurface*    aSurface,
                                         void*             aCache)
   {
     static_cast<SurfaceCacheImpl*>(aCache)->StopTracking(aSurface);
     return PL_DHASH_NEXT;
   }
 
@@ -595,30 +623,31 @@ private:
 
   struct MemoryPressureObserver : public nsIObserver
   {
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*)
     {
       if (sInstance && strcmp(aTopic, "memory-pressure") == 0) {
-        sInstance->DiscardAll();
+        sInstance->DiscardForMemoryPressure();
       }
       return NS_OK;
     }
 
   private:
     virtual ~MemoryPressureObserver() { }
   };
 
 
   nsTArray<CostEntry>                                       mCosts;
   nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches;
   SurfaceTracker                                            mExpirationTracker;
   nsRefPtr<MemoryPressureObserver>                          mMemoryPressureObserver;
+  const uint32_t                                            mDiscardFactor;
   const Cost                                                mMaxCost;
   Cost                                                      mAvailableCost;
   Cost                                                      mLockedCost;
 };
 
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
 
@@ -634,41 +663,48 @@ SurfaceCache::Initialize()
 
   // See gfxPrefs for the default values of these preferences.
 
   // Length of time before an unused surface is removed from the cache, in
   // milliseconds.
   uint32_t surfaceCacheExpirationTimeMS =
     gfxPrefs::ImageMemSurfaceCacheMinExpirationMS();
 
+  // What fraction of the memory used by the surface cache we should discard
+  // when we get a memory pressure notification. This value is interpreted as
+  // 1/N, so 1 means to discard everything, 2 means to discard about half of the
+  // memory we're using, and so forth. We clamp it to avoid division by zero.
+  uint32_t surfaceCacheDiscardFactor =
+    max(gfxPrefs::ImageMemSurfaceCacheDiscardFactor(), 1u);
+
   // Maximum size of the surface cache, in kilobytes.
   uint64_t surfaceCacheMaxSizeKB = gfxPrefs::ImageMemSurfaceCacheMaxSizeKB();
 
   // A knob determining the actual size of the surface cache. Currently the
   // cache is (size of main memory) / (surface cache size factor) KB
   // or (surface cache max size) KB, whichever is smaller. The formula
   // may change in the future, though.
   // For example, a value of 4 would yield a 256MB cache on a 1GB machine.
   // The smallest machines we are likely to run this code on have 256MB
   // of memory, which would yield a 64MB cache on this setting.
-  uint32_t surfaceCacheSizeFactor = gfxPrefs::ImageMemSurfaceCacheSizeFactor();
-
-  // Clamp to avoid division by zero below.
-  surfaceCacheSizeFactor = max(surfaceCacheSizeFactor, 1u);
+  // We clamp this value to avoid division by zero.
+  uint32_t surfaceCacheSizeFactor =
+    max(gfxPrefs::ImageMemSurfaceCacheSizeFactor(), 1u);
 
   // Compute the size of the surface cache.
   uint64_t proposedSize = PR_GetPhysicalMemorySize() / surfaceCacheSizeFactor;
   uint64_t surfaceCacheSizeBytes = min(proposedSize, surfaceCacheMaxSizeKB * 1024);
   uint32_t finalSurfaceCacheSizeBytes =
     min(surfaceCacheSizeBytes, uint64_t(UINT32_MAX));
 
-  // Create the surface cache singleton with the requested expiration time and
-  // size. Note that the size is a limit that the cache may not grow beyond, but
-  // we do not actually allocate any storage for surfaces at this time.
+  // Create the surface cache singleton with the requested settings.  Note that
+  // the size is a limit that the cache may not grow beyond, but we do not
+  // actually allocate any storage for surfaces at this time.
   sInstance = new SurfaceCacheImpl(surfaceCacheExpirationTimeMS,
+                                   surfaceCacheDiscardFactor,
                                    finalSurfaceCacheSizeBytes);
   sInstance->InitMemoryReporter();
 }
 
 /* static */ void
 SurfaceCache::Shutdown()
 {
   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3803,16 +3803,24 @@ pref("image.mem.surfacecache.max_size_kb
 
 // The surface cache's size, within the constraints of the maximum size set
 // above, is determined using a formula based on system capabilities like memory
 // size. The size factor is used to tune this formula. Larger size factors
 // result in smaller caches. The default should be a good balance for most
 // systems.
 pref("image.mem.surfacecache.size_factor", 64);
 
+// How much of the data in the surface cache is discarded when we get a memory
+// pressure notification, as a fraction. The discard factor is interpreted as a
+// reciprocal, so a discard factor of 1 means to discard everything in the
+// surface cache on memory pressure, a discard factor of 2 means to discard half
+// of the data, and so forth. The default should be a good balance for desktop
+// and laptop systems, where we never discard visible images.
+pref("image.mem.surfacecache.discard_factor", 1);
+
 // Whether we decode images on multiple background threads rather than the
 // foreground thread.
 pref("image.multithreaded_decoding.enabled", true);
 
 // How many threads we'll use for multithreaded decoding. If < 0, will be
 // automatically determined based on the system's number of cores.
 pref("image.multithreaded_decoding.limit", -1);