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