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 "SkDiscardableMemoryPool.h"
9 #include "SkDiscardableMemory.h"
10 #include "SkMakeUnique.h"
11 #include "SkMalloc.h"
12 #include "SkMutex.h"
13 #include "SkTInternalLList.h"
14 #include "SkTemplates.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     DiscardableMemoryPool(size_t budget);
31     ~DiscardableMemoryPool() override;
32 
33     std::unique_ptr<SkDiscardableMemory> make(size_t bytes);
34     SkDiscardableMemory* create(size_t bytes) override {
35         return this->make(bytes).release();  // TODO: change API
36     }
37 
38     size_t getRAMUsed() override;
39     void setRAMBudget(size_t budget) override;
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
46     int getCacheHits() override { return fCacheHits; }
47     int getCacheMisses() override { return fCacheMisses; }
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     SkMutex      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 removeFromPool(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(sk_sp<DiscardableMemoryPool> pool, SkAutoFree pointer, size_t bytes);
82     ~PoolDiscardableMemory() override;
83     bool lock() override;
84     void* data() override;
85     void unlock() override;
86     friend class DiscardableMemoryPool;
87 private:
88     SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory);
89     sk_sp<DiscardableMemoryPool> fPool;
90     bool                         fLocked;
91     SkAutoFree                   fPointer;
92     const size_t                 fBytes;
93 };
94 
95 PoolDiscardableMemory::PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool,
96                                              SkAutoFree pointer,
97                                              size_t bytes)
98         : fPool(std::move(pool)), fLocked(true), fPointer(std::move(pointer)), fBytes(bytes) {
99     SkASSERT(fPool != nullptr);
100     SkASSERT(fPointer != nullptr);
101     SkASSERT(fBytes > 0);
102 }
103 
104 PoolDiscardableMemory::~PoolDiscardableMemory() {
105     SkASSERT(!fLocked); // contract for SkDiscardableMemory
106     fPool->removeFromPool(this);
107 }
108 
109 bool PoolDiscardableMemory::lock() {
110     SkASSERT(!fLocked); // contract for SkDiscardableMemory
111     return fPool->lock(this);
112 }
113 
114 void* PoolDiscardableMemory::data() {
115     SkASSERT(fLocked); // contract for SkDiscardableMemory
116     return fPointer.get();
117 }
118 
119 void PoolDiscardableMemory::unlock() {
120     SkASSERT(fLocked); // contract for SkDiscardableMemory
121     fPool->unlock(this);
122 }
123 
124 ////////////////////////////////////////////////////////////////////////////////
125 
126 DiscardableMemoryPool::DiscardableMemoryPool(size_t budget)
127     : fBudget(budget)
128     , fUsed(0) {
129     #if SK_LAZY_CACHE_STATS
130     fCacheHits = 0;
131     fCacheMisses = 0;
132     #endif  // SK_LAZY_CACHE_STATS
133 }
134 DiscardableMemoryPool::~DiscardableMemoryPool() {
135     // PoolDiscardableMemory objects that belong to this pool are
136     // always deleted before deleting this pool since each one has a
137     // ref to the pool.
138     SkASSERT(fList.isEmpty());
139 }
140 
141 void DiscardableMemoryPool::dumpDownTo(size_t budget) {
142     fMutex.assertHeld();
143     if (fUsed <= budget) {
144         return;
145     }
146     using Iter = SkTInternalLList<PoolDiscardableMemory>::Iter;
147     Iter iter;
148     PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart);
149     while ((fUsed > budget) && (cur)) {
150         if (!cur->fLocked) {
151             PoolDiscardableMemory* dm = cur;
152             SkASSERT(dm->fPointer != nullptr);
153             dm->fPointer = nullptr;
154             SkASSERT(fUsed >= dm->fBytes);
155             fUsed -= dm->fBytes;
156             cur = iter.prev();
157             // Purged DMs are taken out of the list.  This saves times
158             // looking them up.  Purged DMs are NOT deleted.
159             fList.remove(dm);
160         } else {
161             cur = iter.prev();
162         }
163     }
164 }
165 
166 std::unique_ptr<SkDiscardableMemory> DiscardableMemoryPool::make(size_t bytes) {
167     SkAutoFree addr(sk_malloc_canfail(bytes));
168     if (nullptr == addr) {
169         return nullptr;
170     }
171     auto dm = skstd::make_unique<PoolDiscardableMemory>(sk_ref_sp(this), std::move(addr), bytes);
172     SkAutoMutexAcquire autoMutexAcquire(fMutex);
173     fList.addToHead(dm.get());
174     fUsed += bytes;
175     this->dumpDownTo(fBudget);
176     return std::move(dm);
177 }
178 
179 void DiscardableMemoryPool::removeFromPool(PoolDiscardableMemory* dm) {
180     SkAutoMutexAcquire autoMutexAcquire(fMutex);
181     // This is called by dm's destructor.
182     if (dm->fPointer != nullptr) {
183         SkASSERT(fUsed >= dm->fBytes);
184         fUsed -= dm->fBytes;
185         fList.remove(dm);
186     } else {
187         SkASSERT(!fList.isInList(dm));
188     }
189 }
190 
191 bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) {
192     SkASSERT(dm != nullptr);
193     SkAutoMutexAcquire autoMutexAcquire(fMutex);
194     if (nullptr == dm->fPointer) {
195         // May have been purged while waiting for lock.
196         #if SK_LAZY_CACHE_STATS
197         ++fCacheMisses;
198         #endif  // SK_LAZY_CACHE_STATS
199         return false;
200     }
201     dm->fLocked = true;
202     fList.remove(dm);
203     fList.addToHead(dm);
204     #if SK_LAZY_CACHE_STATS
205     ++fCacheHits;
206     #endif  // SK_LAZY_CACHE_STATS
207     return true;
208 }
209 
210 void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) {
211     SkASSERT(dm != nullptr);
212     SkAutoMutexAcquire autoMutexAcquire(fMutex);
213     dm->fLocked = false;
214     this->dumpDownTo(fBudget);
215 }
216 
217 size_t DiscardableMemoryPool::getRAMUsed() {
218     return fUsed;
219 }
220 void DiscardableMemoryPool::setRAMBudget(size_t budget) {
221     SkAutoMutexAcquire autoMutexAcquire(fMutex);
222     fBudget = budget;
223     this->dumpDownTo(fBudget);
224 }
225 void DiscardableMemoryPool::dumpPool() {
226     SkAutoMutexAcquire autoMutexAcquire(fMutex);
227     this->dumpDownTo(0);
228 }
229 
230 }  // namespace
231 
232 sk_sp<SkDiscardableMemoryPool> SkDiscardableMemoryPool::Make(size_t size) {
233     return sk_make_sp<DiscardableMemoryPool>(size);
234 }
235 
236 SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() {
237     // Intentionally leak this global pool.
238     static SkDiscardableMemoryPool* global =
239             new DiscardableMemoryPool(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE);
240     return global;
241 }
242