1 /*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/private/base/SkMalloc.h"
9 #include "include/private/base/SkMutex.h"
10 #include "include/private/base/SkTemplates.h"
11 #include "include/private/chromium/SkDiscardableMemory.h"
12 #include "src/base/SkTInternalLList.h"
13 #include "src/lazy/SkDiscardableMemoryPool.h"
14
15 using namespace skia_private;
16
17 // Note:
18 // A PoolDiscardableMemory is memory that is counted in a pool.
19 // A DiscardableMemoryPool is a pool of PoolDiscardableMemorys.
20
21 namespace {
22
23 class PoolDiscardableMemory;
24
25 /**
26 * This non-global pool can be used for unit tests to verify that the
27 * pool works.
28 */
29 class DiscardableMemoryPool : public SkDiscardableMemoryPool {
30 public:
31 DiscardableMemoryPool(size_t budget);
32 ~DiscardableMemoryPool() override;
33
34 std::unique_ptr<SkDiscardableMemory> make(size_t bytes);
create(size_t bytes)35 SkDiscardableMemory* create(size_t bytes) override {
36 return this->make(bytes).release(); // TODO: change API
37 }
38
39 size_t getRAMUsed() override;
40 void setRAMBudget(size_t budget) override;
getRAMBudget()41 size_t getRAMBudget() override { return fBudget; }
42
43 /** purges all unlocked DMs */
44 void dumpPool() override;
45
46 #if SK_LAZY_CACHE_STATS // Defined in SkDiscardableMemoryPool.h
getCacheHits()47 int getCacheHits() override { return fCacheHits; }
getCacheMisses()48 int getCacheMisses() override { return fCacheMisses; }
resetCacheHitsAndMisses()49 void resetCacheHitsAndMisses() override {
50 fCacheHits = fCacheMisses = 0;
51 }
52 int fCacheHits;
53 int fCacheMisses;
54 #endif // SK_LAZY_CACHE_STATS
55
56 private:
57 SkMutex fMutex;
58 size_t fBudget;
59 size_t fUsed;
60 SkTInternalLList<PoolDiscardableMemory> fList;
61
62 /** Function called to free memory if needed */
63 void dumpDownTo(size_t budget);
64 /** called by DiscardableMemoryPool upon destruction */
65 void removeFromPool(PoolDiscardableMemory* dm);
66 /** called by DiscardableMemoryPool::lock() */
67 bool lock(PoolDiscardableMemory* dm);
68 /** called by DiscardableMemoryPool::unlock() */
69 void unlock(PoolDiscardableMemory* dm);
70
71 friend class PoolDiscardableMemory;
72
73 using INHERITED = SkDiscardableMemory::Factory;
74 };
75
76 /**
77 * A PoolDiscardableMemory is a SkDiscardableMemory that relies on
78 * a DiscardableMemoryPool object to manage the memory.
79 */
80 class PoolDiscardableMemory : public SkDiscardableMemory {
81 public:
82 PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool, UniqueVoidPtr pointer, size_t bytes);
83 ~PoolDiscardableMemory() override;
84 bool lock() override;
85 void* data() override;
86 void unlock() override;
87 friend class DiscardableMemoryPool;
88 private:
89 SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory);
90 sk_sp<DiscardableMemoryPool> fPool;
91 bool fLocked;
92 UniqueVoidPtr fPointer;
93 const size_t fBytes;
94 };
95
PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool,UniqueVoidPtr pointer,size_t bytes)96 PoolDiscardableMemory::PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool,
97 UniqueVoidPtr pointer,
98 size_t bytes)
99 : fPool(std::move(pool)), fLocked(true), fPointer(std::move(pointer)), fBytes(bytes) {
100 SkASSERT(fPool != nullptr);
101 SkASSERT(fPointer != nullptr);
102 SkASSERT(fBytes > 0);
103 }
104
~PoolDiscardableMemory()105 PoolDiscardableMemory::~PoolDiscardableMemory() {
106 SkASSERT(!fLocked); // contract for SkDiscardableMemory
107 fPool->removeFromPool(this);
108 }
109
lock()110 bool PoolDiscardableMemory::lock() {
111 SkASSERT(!fLocked); // contract for SkDiscardableMemory
112 return fPool->lock(this);
113 }
114
data()115 void* PoolDiscardableMemory::data() {
116 SkASSERT(fLocked); // contract for SkDiscardableMemory
117 return fPointer.get();
118 }
119
unlock()120 void PoolDiscardableMemory::unlock() {
121 SkASSERT(fLocked); // contract for SkDiscardableMemory
122 fPool->unlock(this);
123 }
124
125 ////////////////////////////////////////////////////////////////////////////////
126
DiscardableMemoryPool(size_t budget)127 DiscardableMemoryPool::DiscardableMemoryPool(size_t budget)
128 : fBudget(budget)
129 , fUsed(0) {
130 #if SK_LAZY_CACHE_STATS
131 fCacheHits = 0;
132 fCacheMisses = 0;
133 #endif // SK_LAZY_CACHE_STATS
134 }
~DiscardableMemoryPool()135 DiscardableMemoryPool::~DiscardableMemoryPool() {
136 // PoolDiscardableMemory objects that belong to this pool are
137 // always deleted before deleting this pool since each one has a
138 // ref to the pool.
139 SkASSERT(fList.isEmpty());
140 }
141
dumpDownTo(size_t budget)142 void DiscardableMemoryPool::dumpDownTo(size_t budget) {
143 fMutex.assertHeld();
144 if (fUsed <= budget) {
145 return;
146 }
147 using Iter = SkTInternalLList<PoolDiscardableMemory>::Iter;
148 Iter iter;
149 PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart);
150 while ((fUsed > budget) && (cur)) {
151 if (!cur->fLocked) {
152 PoolDiscardableMemory* dm = cur;
153 SkASSERT(dm->fPointer != nullptr);
154 dm->fPointer = nullptr;
155 SkASSERT(fUsed >= dm->fBytes);
156 fUsed -= dm->fBytes;
157 cur = iter.prev();
158 // Purged DMs are taken out of the list. This saves times
159 // looking them up. Purged DMs are NOT deleted.
160 fList.remove(dm);
161 } else {
162 cur = iter.prev();
163 }
164 }
165 }
166
make(size_t bytes)167 std::unique_ptr<SkDiscardableMemory> DiscardableMemoryPool::make(size_t bytes) {
168 UniqueVoidPtr addr(sk_malloc_canfail(bytes));
169 if (nullptr == addr) {
170 return nullptr;
171 }
172 auto dm = std::make_unique<PoolDiscardableMemory>(sk_ref_sp(this), std::move(addr), bytes);
173 SkAutoMutexExclusive autoMutexAcquire(fMutex);
174 fList.addToHead(dm.get());
175 fUsed += bytes;
176 this->dumpDownTo(fBudget);
177 return dm;
178 }
179
removeFromPool(PoolDiscardableMemory * dm)180 void DiscardableMemoryPool::removeFromPool(PoolDiscardableMemory* dm) {
181 SkAutoMutexExclusive autoMutexAcquire(fMutex);
182 // This is called by dm's destructor.
183 if (dm->fPointer != nullptr) {
184 SkASSERT(fUsed >= dm->fBytes);
185 fUsed -= dm->fBytes;
186 fList.remove(dm);
187 } else {
188 SkASSERT(!fList.isInList(dm));
189 }
190 }
191
lock(PoolDiscardableMemory * dm)192 bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) {
193 SkASSERT(dm != nullptr);
194 SkAutoMutexExclusive autoMutexAcquire(fMutex);
195 if (nullptr == dm->fPointer) {
196 // May have been purged while waiting for lock.
197 #if SK_LAZY_CACHE_STATS
198 ++fCacheMisses;
199 #endif // SK_LAZY_CACHE_STATS
200 return false;
201 }
202 dm->fLocked = true;
203 fList.remove(dm);
204 fList.addToHead(dm);
205 #if SK_LAZY_CACHE_STATS
206 ++fCacheHits;
207 #endif // SK_LAZY_CACHE_STATS
208 return true;
209 }
210
unlock(PoolDiscardableMemory * dm)211 void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) {
212 SkASSERT(dm != nullptr);
213 SkAutoMutexExclusive autoMutexAcquire(fMutex);
214 dm->fLocked = false;
215 this->dumpDownTo(fBudget);
216 }
217
getRAMUsed()218 size_t DiscardableMemoryPool::getRAMUsed() {
219 return fUsed;
220 }
setRAMBudget(size_t budget)221 void DiscardableMemoryPool::setRAMBudget(size_t budget) {
222 SkAutoMutexExclusive autoMutexAcquire(fMutex);
223 fBudget = budget;
224 this->dumpDownTo(fBudget);
225 }
dumpPool()226 void DiscardableMemoryPool::dumpPool() {
227 SkAutoMutexExclusive autoMutexAcquire(fMutex);
228 this->dumpDownTo(0);
229 }
230
231 } // namespace
232
Make(size_t size)233 sk_sp<SkDiscardableMemoryPool> SkDiscardableMemoryPool::Make(size_t size) {
234 return sk_make_sp<DiscardableMemoryPool>(size);
235 }
236
SkGetGlobalDiscardableMemoryPool()237 SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() {
238 // Intentionally leak this global pool.
239 static SkDiscardableMemoryPool* global =
240 new DiscardableMemoryPool(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE);
241 return global;
242 }
243