Chromium Code Reviews| Index: base/memory/discardable_memory_provider.cc |
| diff --git a/base/memory/discardable_memory_provider.cc b/base/memory/discardable_memory_provider.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..9be289b9c604abbf79b8516d83014ddfe60502f5 |
| --- /dev/null |
| +++ b/base/memory/discardable_memory_provider.cc |
| @@ -0,0 +1,217 @@ |
| +// Copyright 2013 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_provider.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/containers/hash_tables.h" |
| +#include "base/containers/mru_cache.h" |
| +#include "base/debug/trace_event.h" |
| +#include "base/memory/discardable_memory.h" |
| +#include "base/synchronization/lock.h" |
| +#include "base/sys_info.h" |
| + |
| +namespace base { |
| +namespace internal { |
| + |
| +namespace { |
| + |
| +// If this is given a valid value via SetInstanceForTest, this pointer will be |
| +// returned by GetInstance rather than the usual singleton. |
| +static DiscardableMemoryProvider* s_provider_for_test = NULL; |
| + |
| +// This is admittedly pretty magical. It's approximately enough memory for two |
| +// 2560x1600 images. |
| +static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024; |
| +static const size_t kDefaultBytesToReclaimUnderModeratePressure = |
| + kDefaultDiscardableMemoryLimit / 2; |
| + |
| +} // namespace |
| + |
| +DiscardableMemoryProvider::DiscardableMemoryProvider() |
| + : allocations_(AllocationMap::NO_AUTO_EVICT), |
| + bytes_allocated_(0), |
| + discardable_memory_limit_(kDefaultDiscardableMemoryLimit), |
| + bytes_to_reclaim_under_moderate_pressure_( |
| + kDefaultBytesToReclaimUnderModeratePressure), |
| + enforcing_policy_(false), |
| + memory_pressure_listener_( |
| + base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure)) { |
| +} |
| + |
| +DiscardableMemoryProvider::~DiscardableMemoryProvider() { |
| + AutoLock lock(allocations_lock_); |
| + AllocationMap::iterator it = allocations_.begin(); |
| + for (; it != allocations_.end(); ++it) |
|
willchan no longer on Chromium
2013/10/01 18:47:25
Mind putting braces around this? I know it works,
reveman
2013/10/09 22:40:24
Done.
|
| + if (it->first->memory_) |
| + it->first->Deallocate(); |
| +} |
| + |
| +// static |
| +DiscardableMemoryProvider* DiscardableMemoryProvider::GetInstance() { |
| + if (s_provider_for_test) |
| + return s_provider_for_test; |
| + return Singleton<DiscardableMemoryProvider>::get(); |
| +} |
| + |
| +// static |
| +void DiscardableMemoryProvider::SetInstanceForTest( |
| + DiscardableMemoryProvider* provider) { |
| + s_provider_for_test = provider; |
| +} |
| + |
| +// static |
| +void DiscardableMemoryProvider::NotifyMemoryPressure( |
|
willchan no longer on Chromium
2013/10/01 18:47:25
OK, there's a bug here. NotifyMemoryPressure will
reveman
2013/10/09 22:40:24
This is supposed to be fixed in my latest patch. P
|
| + MemoryPressureListener::MemoryPressureLevel pressure_level) { |
| + switch (pressure_level) { |
| + case MemoryPressureListener::MEMORY_PRESSURE_MODERATE: |
| + DiscardableMemoryProvider::GetInstance()->PurgeLRU(); |
| + break; |
| + case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL: |
| + DiscardableMemoryProvider::GetInstance()->PurgeAll(); |
| + break; |
| + default: |
| + NOTREACHED(); |
|
reveman
2013/10/09 22:40:24
Note: I removed the default case and moved the NOT
|
| + } |
| +} |
| + |
| +void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + discardable_memory_limit_ = bytes; |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +size_t DiscardableMemoryProvider::discardable_memory_limit() const { |
| + AutoLock lock(bytes_allocated_lock_); |
| + return discardable_memory_limit_; |
| +} |
| + |
| +void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure( |
| + size_t bytes) { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + bytes_to_reclaim_under_moderate_pressure_ = bytes; |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +size_t DiscardableMemoryProvider:: |
| + bytes_to_reclaim_under_moderate_pressure() const { |
| + AutoLock lock(bytes_allocated_lock_); |
| + return bytes_to_reclaim_under_moderate_pressure_; |
| +} |
| + |
| +void DiscardableMemoryProvider::Register(DiscardableMemory* discardable) { |
| + DCHECK(allocations_.Peek(discardable) == allocations_.end()); |
| + { |
| + AutoLock lock(allocations_lock_); |
| + allocations_.Put(discardable, true); |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +void DiscardableMemoryProvider::Unregister(DiscardableMemory* discardable) { |
| + { |
| + AutoLock lock(allocations_lock_); |
| + AllocationMap::iterator it = allocations_.Peek(discardable); |
| + if (it != allocations_.end()) |
| + allocations_.Erase(it); |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +void DiscardableMemoryProvider::DidAllocate(size_t bytes) { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + bytes_allocated_ += bytes; |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +void DiscardableMemoryProvider::DidDeallocate(size_t bytes) { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + DCHECK_LE(bytes, bytes_allocated_); |
| + bytes_allocated_ -= bytes; |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +bool DiscardableMemoryProvider::DidAccess(DiscardableMemory* discardable) { |
| + AutoLock lock(allocations_lock_); |
| + // NB: |allocations_| is an MRU cache, and use of |Get| here updates that |
| + // cache. |
| + AllocationMap::iterator it = allocations_.Get(discardable); |
| + return it != allocations_.end(); |
| +} |
| + |
| +void DiscardableMemoryProvider::PurgeAll() { |
| + TRACE_EVENT0("base", "DiscardableMemoryProvider::PurgeAll"); |
| + |
| + AutoLock lock(allocations_lock_); |
| + 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 DiscardableMemoryProvider::PurgeLRU() { |
| + size_t limit = 0; |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + if (bytes_to_reclaim_under_moderate_pressure_ == 0) |
| + return; |
| + |
| + if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_) |
| + limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_; |
| + } |
| + |
| + TRACE_EVENT0("base", "DiscardableMemoryProvider::PurgeLRU"); |
| + AutoLock lock(allocations_lock_); |
| + 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_); |
| + AutoLock bytes_lock(bytes_allocated_lock_); |
| + if (bytes_allocated_ <= limit) |
| + break; |
| + } |
| +} |
| + |
| +void DiscardableMemoryProvider::EnforcePolicy() { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + if (enforcing_policy_) |
| + return; |
| + } |
| + |
| + bool exceeded_bound = false; |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + enforcing_policy_ = true; |
| + if (discardable_memory_limit_ == 0) { |
| + enforcing_policy_ = false; |
| + return; |
| + } |
| + exceeded_bound = bytes_allocated_ > discardable_memory_limit_; |
| + } |
| + |
| + if (exceeded_bound) |
| + PurgeLRU(); |
| + |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + enforcing_policy_ = false; |
| + } |
| +} |
| + |
| +} // namespace internal |
| +} // namespace base |