Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/memory/discardable_memory_provider.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/containers/hash_tables.h" | |
| 9 #include "base/containers/mru_cache.h" | |
| 10 #include "base/debug/trace_event.h" | |
| 11 #include "base/memory/discardable_memory.h" | |
| 12 #include "base/synchronization/lock.h" | |
| 13 #include "base/sys_info.h" | |
| 14 | |
| 15 namespace base { | |
| 16 namespace internal { | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 // If this is given a valid value via SetInstanceForTest, this pointer will be | |
| 21 // returned by GetInstance rather than the usual singleton. | |
| 22 static DiscardableMemoryProvider* s_provider_for_test = NULL; | |
| 23 | |
| 24 // This is admittedly pretty magical. It's approximately enough memory for two | |
| 25 // 2560x1600 images. | |
| 26 static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024; | |
| 27 static const size_t kDefaultBytesToReclaimUnderModeratePressure = | |
| 28 kDefaultDiscardableMemoryLimit / 2; | |
| 29 | |
| 30 } // namespace | |
| 31 | |
| 32 DiscardableMemoryProvider::DiscardableMemoryProvider() | |
| 33 : allocations_(AllocationMap::NO_AUTO_EVICT), | |
| 34 bytes_allocated_(0), | |
| 35 discardable_memory_limit_(kDefaultDiscardableMemoryLimit), | |
| 36 bytes_to_reclaim_under_moderate_pressure_( | |
| 37 kDefaultBytesToReclaimUnderModeratePressure), | |
| 38 enforcing_policy_(false), | |
| 39 memory_pressure_listener_( | |
| 40 base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure)) { | |
| 41 } | |
| 42 | |
| 43 DiscardableMemoryProvider::~DiscardableMemoryProvider() { | |
| 44 DCHECK_EQ(0u, allocations_.size()); | |
| 45 DCHECK_EQ(0u, bytes_allocated_); | |
| 46 } | |
| 47 | |
| 48 // static | |
| 49 DiscardableMemoryProvider* DiscardableMemoryProvider::GetInstance() { | |
| 50 if (s_provider_for_test) | |
| 51 return s_provider_for_test; | |
| 52 return Singleton<DiscardableMemoryProvider>::get(); | |
|
willchan no longer on Chromium
2013/10/17 02:06:13
WDYT about using a LeakyLazyInstance instead? Is t
reveman
2013/10/19 21:17:04
Not really. I guess we need to use a LeakyLazyInst
willchan no longer on Chromium
2013/10/21 18:30:27
Not deleting on shutdown is preferable. It avoids
reveman
2013/10/22 00:11:41
Thanks for the explanation. That makes sense. It's
| |
| 53 } | |
| 54 | |
| 55 // static | |
| 56 void DiscardableMemoryProvider::SetInstanceForTest( | |
| 57 DiscardableMemoryProvider* provider) { | |
| 58 s_provider_for_test = provider; | |
| 59 } | |
| 60 | |
| 61 // static | |
| 62 void DiscardableMemoryProvider::NotifyMemoryPressure( | |
| 63 MemoryPressureListener::MemoryPressureLevel pressure_level) { | |
| 64 switch (pressure_level) { | |
| 65 case MemoryPressureListener::MEMORY_PRESSURE_MODERATE: | |
| 66 DiscardableMemoryProvider::GetInstance()->Purge(); | |
| 67 return; | |
| 68 case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL: | |
| 69 DiscardableMemoryProvider::GetInstance()->PurgeAll(); | |
| 70 return; | |
| 71 } | |
| 72 | |
| 73 NOTREACHED(); | |
| 74 } | |
| 75 | |
| 76 void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) { | |
| 77 { | |
| 78 AutoLock lock(bytes_allocated_lock_); | |
| 79 discardable_memory_limit_ = bytes; | |
| 80 } | |
| 81 EnforcePolicy(); | |
| 82 } | |
| 83 | |
| 84 void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure( | |
| 85 size_t bytes) { | |
| 86 { | |
| 87 AutoLock lock(bytes_allocated_lock_); | |
| 88 bytes_to_reclaim_under_moderate_pressure_ = bytes; | |
| 89 } | |
| 90 EnforcePolicy(); | |
| 91 } | |
| 92 | |
| 93 void DiscardableMemoryProvider::Register( | |
| 94 const DiscardableMemory* discardable, size_t bytes) { | |
| 95 AutoLock lock(allocations_lock_); | |
| 96 DCHECK(allocations_.Peek(discardable) == allocations_.end()); | |
| 97 allocations_.Put(discardable, Allocation(bytes)); | |
| 98 } | |
| 99 | |
| 100 void DiscardableMemoryProvider::Unregister( | |
| 101 const DiscardableMemory* discardable) { | |
| 102 AutoLock lock(allocations_lock_); | |
|
willchan no longer on Chromium
2013/10/17 02:06:13
You acquire this lock here first. Later on, you ma
reveman
2013/10/19 21:17:04
Yes, the use of multiple locks here is broken. Red
| |
| 103 AllocationMap::iterator it = allocations_.Peek(discardable); | |
| 104 if (it == allocations_.end()) | |
| 105 return; | |
| 106 | |
| 107 if (it->second.memory) { | |
| 108 AutoLock bytes_lock(bytes_allocated_lock_); | |
| 109 size_t bytes = it->second.bytes; | |
| 110 DCHECK_LE(bytes, bytes_allocated_); | |
| 111 bytes_allocated_ -= bytes; | |
| 112 free(it->second.memory); | |
| 113 } | |
| 114 allocations_.Erase(it); | |
| 115 } | |
| 116 | |
| 117 scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire( | |
| 118 const DiscardableMemory* discardable, | |
| 119 bool* purged) { | |
| 120 AutoLock lock(allocations_lock_); | |
| 121 // NB: |allocations_| is an MRU cache, and use of |Get| here updates that | |
| 122 // cache. | |
| 123 AllocationMap::iterator it = allocations_.Get(discardable); | |
| 124 CHECK(it != allocations_.end()); | |
| 125 | |
| 126 if (it->second.memory) { | |
| 127 scoped_ptr<uint8, FreeDeleter> memory(it->second.memory); | |
| 128 it->second.memory = NULL; | |
| 129 *purged = false; | |
| 130 return memory.Pass(); | |
| 131 } | |
| 132 | |
| 133 size_t bytes = it->second.bytes; | |
| 134 if (!bytes) | |
| 135 return scoped_ptr<uint8, FreeDeleter>(); | |
| 136 | |
| 137 AutoLock bytes_lock(bytes_allocated_lock_); | |
| 138 | |
| 139 if (discardable_memory_limit_) { | |
| 140 if (bytes > discardable_memory_limit_) | |
| 141 return scoped_ptr<uint8, FreeDeleter>(); | |
| 142 | |
| 143 size_t limit = discardable_memory_limit_ - bytes; | |
| 144 PurgeLRU(limit); | |
| 145 | |
| 146 if (bytes_allocated_ > limit) | |
| 147 return scoped_ptr<uint8, FreeDeleter>(); | |
| 148 } | |
| 149 | |
| 150 bytes_allocated_ += bytes; | |
| 151 *purged = true; | |
| 152 return scoped_ptr<uint8, FreeDeleter>(static_cast<uint8*>(malloc(bytes))); | |
| 153 } | |
| 154 | |
| 155 void DiscardableMemoryProvider::Release( | |
| 156 const DiscardableMemory* discardable, | |
| 157 scoped_ptr<uint8, FreeDeleter> memory) { | |
| 158 { | |
| 159 AutoLock lock(allocations_lock_); | |
| 160 // NB: |allocations_| is an MRU cache, and use of |Get| here updates that | |
| 161 // cache. | |
| 162 AllocationMap::iterator it = allocations_.Get(discardable); | |
| 163 CHECK(it != allocations_.end()); | |
| 164 | |
| 165 DCHECK(!it->second.memory); | |
| 166 it->second.memory = memory.release(); | |
| 167 } | |
| 168 | |
| 169 EnforcePolicy(); | |
| 170 } | |
| 171 | |
| 172 bool DiscardableMemoryProvider::IsRegisteredForTest( | |
| 173 const DiscardableMemory* discardable) { | |
| 174 AutoLock lock(allocations_lock_); | |
| 175 AllocationMap::iterator it = allocations_.Peek(discardable); | |
| 176 return it != allocations_.end(); | |
| 177 } | |
| 178 | |
| 179 bool DiscardableMemoryProvider::CanBePurgedForTest( | |
| 180 const DiscardableMemory* discardable) { | |
| 181 AutoLock lock(allocations_lock_); | |
| 182 AllocationMap::iterator it = allocations_.Peek(discardable); | |
| 183 return it != allocations_.end() && it->second.memory; | |
| 184 } | |
| 185 | |
| 186 size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() { | |
| 187 AutoLock lock(bytes_allocated_lock_); | |
| 188 return bytes_allocated_; | |
| 189 } | |
| 190 | |
| 191 void DiscardableMemoryProvider::PurgeLRU(size_t limit) { | |
| 192 TRACE_EVENT1("base", "DiscardableMemoryProvider::PurgeLRU", "limit", limit); | |
| 193 | |
| 194 allocations_lock_.AssertAcquired(); | |
| 195 bytes_allocated_lock_.AssertAcquired(); | |
| 196 | |
| 197 for (AllocationMap::reverse_iterator it = allocations_.rbegin(); | |
| 198 it != allocations_.rend(); | |
| 199 ++it) { | |
| 200 if (bytes_allocated_ <= limit) | |
| 201 break; | |
| 202 if (!it->second.memory) | |
| 203 continue; | |
| 204 | |
| 205 size_t bytes = it->second.bytes; | |
| 206 DCHECK_LE(bytes, bytes_allocated_); | |
| 207 bytes_allocated_ -= bytes; | |
| 208 free(it->second.memory); | |
| 209 it->second.memory = NULL; | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 void DiscardableMemoryProvider::Purge() { | |
| 214 size_t limit = 0; | |
| 215 AutoLock bytes_lock(bytes_allocated_lock_); | |
| 216 if (bytes_to_reclaim_under_moderate_pressure_ == 0) | |
| 217 return; | |
| 218 | |
| 219 if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_) | |
| 220 limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_; | |
| 221 | |
| 222 AutoLock lock(allocations_lock_); | |
| 223 PurgeLRU(limit); | |
| 224 } | |
| 225 | |
| 226 void DiscardableMemoryProvider::PurgeAll() { | |
| 227 AutoLock bytes_lock(bytes_allocated_lock_); | |
| 228 AutoLock lock(allocations_lock_); | |
| 229 PurgeLRU(0); | |
| 230 } | |
| 231 | |
| 232 void DiscardableMemoryProvider::EnforcePolicy() { | |
| 233 { | |
| 234 AutoLock lock(bytes_allocated_lock_); | |
| 235 if (enforcing_policy_) | |
| 236 return; | |
| 237 } | |
| 238 | |
| 239 bool exceeded_bound = false; | |
| 240 { | |
| 241 AutoLock lock(bytes_allocated_lock_); | |
| 242 enforcing_policy_ = true; | |
| 243 if (discardable_memory_limit_ == 0) { | |
| 244 enforcing_policy_ = false; | |
| 245 return; | |
| 246 } | |
| 247 exceeded_bound = bytes_allocated_ > discardable_memory_limit_; | |
| 248 } | |
| 249 | |
| 250 if (exceeded_bound) | |
| 251 Purge(); | |
| 252 | |
| 253 { | |
| 254 AutoLock lock(bytes_allocated_lock_); | |
| 255 enforcing_policy_ = false; | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 } // namespace internal | |
| 260 } // namespace base | |
| OLD | NEW |