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