Index: base/memory/discardable_memory_emulated.cc |
diff --git a/base/memory/discardable_memory_emulated.cc b/base/memory/discardable_memory_emulated.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..17f56360e20f4dd92ec10417582f0b841642e3d4 |
--- /dev/null |
+++ b/base/memory/discardable_memory_emulated.cc |
@@ -0,0 +1,213 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/memory/discardable_memory.h" |
+ |
+#include "base/containers/hash_tables.h" |
+#include "base/containers/mru_cache.h" |
+#include "base/memory/singleton.h" |
+#include "base/synchronization/lock.h" |
+#include "base/sys_info.h" |
+ |
+#if defined(COMPILER_GCC) |
+namespace BASE_HASH_NAMESPACE { |
+template <> |
+struct hash<base::DiscardableMemory*> { |
+ size_t operator()(base::DiscardableMemory* ptr) const { |
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr)); |
+ } |
+}; |
+} // namespace BASE_HASH_NAMESPACE |
+#endif // COMPILER |
+ |
+namespace base { |
+ |
+namespace { |
+ |
+// These are definitely magical. Is there a nice way to set them? |
+static size_t kCacheLimitUpperBound = 32 * 1024 * 1024; |
+static size_t kCacheLimitPurgeBound = 4 * 1024 * 1024; |
+static int64 kSystemMemoryLowerBound = 256 * 1024 * 1024; |
+ |
+static bool SystemIsUnderMemoryPressure() { |
+ return SysInfo::AmountOfAvailablePhysicalMemory() < |
+ kSystemMemoryLowerBound; |
Avi (use Gerrit)
2013/06/12 14:56:49
Way too magical. Does this function belong in SysI
|
+} |
+ |
+typedef HashingMRUCache<DiscardableMemory*, bool> AllocationMap; |
Avi (use Gerrit)
2013/06/12 14:56:49
bool? If that's just a placeholder, say so.
|
+ |
+} // namespace |
+ |
+// This provider is accessed from multiple threads, so some care needs to be |
+// taken to ensure thread safety. In general, the policy has been that |
+// allocations are initiated by client code (via Lock or InitializeAndLock), |
+// and deallocations are initiated by the provider. Given this policy, the |
+// provider assumes that the lock has already been aquired by the provider |
+// itself in the Purge/Policy/DidDeallocate methods, but does not make this |
+// assumption in the rest of the interface and aquires the lock as appropriate. |
+class DiscardableMemoryProvider { |
Avi (use Gerrit)
2013/06/12 14:56:49
Give the member functions explanatory comments. Me
Avi (use Gerrit)
2013/06/12 14:56:49
DiscardableMemoryProvider desperately needs a unit
|
+ public: |
+ DiscardableMemoryProvider() |
+ : allocations_(AllocationMap::NO_AUTO_EVICT), |
+ bytes_allocated_(0) { |
+ } |
+ |
+ virtual ~DiscardableMemoryProvider() { |
+ AutoLock lock(lock_); |
+ AllocationMap::iterator it = allocations_.begin(); |
+ for (; it != allocations_.end(); ++it) |
+ if (it->first->Memory()) |
+ it->first->Deallocate(); |
+ } |
+ |
+ static DiscardableMemoryProvider* GetInstance() { |
+ return Singleton<DiscardableMemoryProvider>::get(); |
+ } |
+ |
+ void Register(DiscardableMemory* discardable) { |
+ Unregister(discardable); |
Avi (use Gerrit)
2013/06/12 14:56:49
Why the Unregister? Register is only called from D
|
+ { |
+ AutoLock lock(lock_); |
+ allocations_.Put(discardable, true); |
+ EnforcePolicy(); |
+ } |
+ } |
+ |
+ void Unregister(DiscardableMemory* discardable) { |
+ if (discardable->is_locked()) |
+ discardable->Unlock(); |
+ { |
+ AutoLock lock(lock_); |
+ if (discardable->memory_) |
+ discardable->Deallocate(); |
Avi (use Gerrit)
2013/06/12 14:56:49
Eh...
Unregister is only called by ~DiscardableMe
|
+ AllocationMap::iterator it = allocations_.Peek(discardable); |
+ if (it != allocations_.end()) { |
+ allocations_.Erase(it); |
+ EnforcePolicy(); |
+ } |
+ } |
+ } |
+ |
+ void DidAllocate(size_t bytes) { |
+ AutoLock lock(lock_); |
+ bytes_allocated_ += bytes; |
+ EnforcePolicy(); |
+ } |
+ |
+ void DidDeallocate(size_t bytes) { |
+ lock_.AssertAcquired(); |
+ DCHECK(bytes <= bytes_allocated_); |
+ bytes_allocated_ -= bytes; |
+ EnforcePolicy(); |
+ } |
+ |
+ void DidAccess(DiscardableMemory* discardable) { |
+ AutoLock lock(lock_); |
+ allocations_.Get(discardable); |
+ } |
+ |
+ void ForcePurge() { |
+ AutoLock lock(lock_); |
+ PurgeAll(); |
+ } |
+ |
+ void PurgeAll() { |
+ AllocationMap::iterator it = allocations_.begin(); |
+ for (; it != allocations_.end(); ++it) { |
+ if (it->first->memory_ && !it->first->is_locked()) { |
+ it->first->Deallocate(); |
+ DCHECK(!it->first->memory_); |
+ } |
+ } |
+ } |
+ |
+ void PurgeLRU() { |
+ AllocationMap::reverse_iterator it = allocations_.rbegin(); |
+ for(; it != allocations_.rend(); ++it) { |
+ if (!it->first->memory_ || !it->first->is_locked()) |
+ continue; |
+ it->first->Deallocate(); |
+ DCHECK(!it->first->memory_); |
+ if (bytes_allocated_ < kCacheLimitPurgeBound) |
cpu_(ooo_6.6-7.5)
2013/06/13 20:16:06
I was expecting freeing N megs, not freeing until
|
+ break; |
+ } |
+ } |
+ |
+ void EnforcePolicy() { |
+ if (SystemIsUnderMemoryPressure()) |
+ PurgeAll(); |
cpu_(ooo_6.6-7.5)
2013/06/13 20:16:06
again, surprised on the full purge.
|
+ else if (bytes_allocated_ > kCacheLimitUpperBound) |
+ PurgeLRU(); |
+ } |
+ |
+ private: |
+ AllocationMap allocations_; |
+ bool visible_; |
+ size_t bytes_allocated_; |
+ Lock lock_; |
Avi (use Gerrit)
2013/06/12 14:56:49
Member variables need comment love too. In particu
|
+ |
+ DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryProvider); |
+}; |
+ |
+DiscardableMemory::~DiscardableMemory() { |
+ DiscardableMemoryProvider::GetInstance()->Unregister(this); |
+} |
+ |
+bool DiscardableMemory::InitializeAndLock(size_t size) { |
+ DiscardableMemoryProvider::GetInstance()->Register(this); |
+ size_ = size; |
+ return Lock() != DISCARDABLE_MEMORY_FAILED; |
Avi (use Gerrit)
2013/06/12 14:56:49
LockDiscardableMemoryStatus status = Lock();
DCHEC
|
+} |
+ |
+LockDiscardableMemoryStatus DiscardableMemory::Lock() { |
+ DCHECK(!is_locked_); |
+ DiscardableMemoryProvider::GetInstance()->DidAccess(this); |
+ is_locked_ = true; |
+ |
+ if (memory_) |
+ return DISCARDABLE_MEMORY_SUCCESS; |
+ |
+ if (Allocate()) |
+ return DISCARDABLE_MEMORY_PURGED; |
+ |
+ return DISCARDABLE_MEMORY_FAILED; |
+} |
+ |
+void DiscardableMemory::Unlock() { |
+ DCHECK(is_locked_); |
+ DiscardableMemoryProvider::GetInstance()->DidAccess(this); |
+ is_locked_ = false; |
+} |
+ |
+bool DiscardableMemory::Allocate() { |
+ DCHECK(!memory_); |
+ memory_ = malloc(size_ * sizeof(char)); |
Avi (use Gerrit)
2013/06/12 14:56:49
sizeof(char) is defined by the standard to be 1 (C
|
+ if (memory_) |
+ DiscardableMemoryProvider::GetInstance()->DidAllocate(size_); |
+ return memory_; |
+} |
+ |
+void DiscardableMemory::Deallocate() { |
+ DCHECK(memory_); |
+ free(memory_); |
+ memory_ = NULL; |
+ DiscardableMemoryProvider::GetInstance()->DidDeallocate(size_); |
+} |
+ |
+ |
+// static |
+bool DiscardableMemory::PurgeForTestingSupported() { |
+ return true; |
+} |
+ |
+// static |
+void DiscardableMemory::PurgeForTesting() { |
+ Purge(); |
+} |
+ |
+void DiscardableMemory::Purge() { |
+ DiscardableMemoryProvider::GetInstance()->ForcePurge(); |
+} |
+ |
+} // namespace base |