/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkDiscardableMemoryPool.h" #include "SkOnce.h" // Note: // A PoolDiscardableMemory is memory that is counted in a pool. // A DiscardableMemoryPool is a pool of PoolDiscardableMemorys. /** * A SkPoolDiscardableMemory is a SkDiscardableMemory that relies on * a SkDiscardableMemoryPool object to manage the memory. */ class SkPoolDiscardableMemory : public SkDiscardableMemory { public: SkPoolDiscardableMemory(SkDiscardableMemoryPool* pool, void* pointer, size_t bytes); virtual ~SkPoolDiscardableMemory(); virtual bool lock() SK_OVERRIDE; virtual void* data() SK_OVERRIDE; virtual void unlock() SK_OVERRIDE; friend class SkDiscardableMemoryPool; private: SK_DECLARE_INTERNAL_LLIST_INTERFACE(SkPoolDiscardableMemory); SkDiscardableMemoryPool* const fPool; bool fLocked; void* fPointer; const size_t fBytes; }; SkPoolDiscardableMemory::SkPoolDiscardableMemory(SkDiscardableMemoryPool* pool, void* pointer, size_t bytes) : fPool(pool) , fLocked(true) , fPointer(pointer) , fBytes(bytes) { SkASSERT(fPool != NULL); SkASSERT(fPointer != NULL); SkASSERT(fBytes > 0); fPool->ref(); } SkPoolDiscardableMemory::~SkPoolDiscardableMemory() { SkASSERT(!fLocked); // contract for SkDiscardableMemory fPool->free(this); fPool->unref(); } bool SkPoolDiscardableMemory::lock() { SkASSERT(!fLocked); // contract for SkDiscardableMemory return fPool->lock(this); } void* SkPoolDiscardableMemory::data() { SkASSERT(fLocked); // contract for SkDiscardableMemory return fPointer; } void SkPoolDiscardableMemory::unlock() { SkASSERT(fLocked); // contract for SkDiscardableMemory fPool->unlock(this); } //////////////////////////////////////////////////////////////////////////////// SkDiscardableMemoryPool::SkDiscardableMemoryPool(size_t budget, SkBaseMutex* mutex) : fMutex(mutex) , fBudget(budget) , fUsed(0) { #if LAZY_CACHE_STATS fCacheHits = 0; fCacheMisses = 0; #endif // LAZY_CACHE_STATS } SkDiscardableMemoryPool::~SkDiscardableMemoryPool() { // SkPoolDiscardableMemory objects that belong to this pool are // always deleted before deleting this pool since each one has a // ref to the pool. SkASSERT(fList.isEmpty()); } void SkDiscardableMemoryPool::dumpDownTo(size_t budget) { // assert((NULL = fMutex) || fMutex->isLocked()); // TODO(halcanary) implement bool fMutex::isLocked(). // WARNING: only call this function after aquiring lock. if (fUsed <= budget) { return; } typedef SkTInternalLList::Iter Iter; Iter iter; SkPoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart); while ((fUsed > budget) && (NULL != cur)) { if (!cur->fLocked) { SkPoolDiscardableMemory* dm = cur; SkASSERT(dm->fPointer != NULL); sk_free(dm->fPointer); dm->fPointer = NULL; SkASSERT(fUsed >= dm->fBytes); fUsed -= dm->fBytes; cur = iter.prev(); // Purged DMs are taken out of the list. This saves times // looking them up. Purged DMs are NOT deleted. fList.remove(dm); } else { cur = iter.prev(); } } } SkDiscardableMemory* SkDiscardableMemoryPool::create(size_t bytes) { void* addr = sk_malloc_flags(bytes, 0); if (NULL == addr) { return NULL; } SkPoolDiscardableMemory* dm = SkNEW_ARGS(SkPoolDiscardableMemory, (this, addr, bytes)); SkAutoMutexAcquire autoMutexAcquire(fMutex); fList.addToHead(dm); fUsed += bytes; this->dumpDownTo(fBudget); return dm; } void SkDiscardableMemoryPool::free(SkPoolDiscardableMemory* dm) { // This is called by dm's destructor. if (dm->fPointer != NULL) { SkAutoMutexAcquire autoMutexAcquire(fMutex); sk_free(dm->fPointer); dm->fPointer = NULL; SkASSERT(fUsed >= dm->fBytes); fUsed -= dm->fBytes; fList.remove(dm); } else { SkASSERT(!fList.isInList(dm)); } } bool SkDiscardableMemoryPool::lock(SkPoolDiscardableMemory* dm) { SkASSERT(dm != NULL); if (NULL == dm->fPointer) { #if LAZY_CACHE_STATS SkAutoMutexAcquire autoMutexAcquire(fMutex); ++fCacheMisses; #endif // LAZY_CACHE_STATS return false; } SkAutoMutexAcquire autoMutexAcquire(fMutex); if (NULL == dm->fPointer) { // May have been purged while waiting for lock. #if LAZY_CACHE_STATS ++fCacheMisses; #endif // LAZY_CACHE_STATS return false; } dm->fLocked = true; fList.remove(dm); fList.addToHead(dm); #if LAZY_CACHE_STATS ++fCacheHits; #endif // LAZY_CACHE_STATS return true; } void SkDiscardableMemoryPool::unlock(SkPoolDiscardableMemory* dm) { SkASSERT(dm != NULL); SkAutoMutexAcquire autoMutexAcquire(fMutex); dm->fLocked = false; this->dumpDownTo(fBudget); } size_t SkDiscardableMemoryPool::getRAMUsed() { return fUsed; } void SkDiscardableMemoryPool::setRAMBudget(size_t budget) { SkAutoMutexAcquire autoMutexAcquire(fMutex); fBudget = budget; this->dumpDownTo(fBudget); } void SkDiscardableMemoryPool::dumpPool() { SkAutoMutexAcquire autoMutexAcquire(fMutex); this->dumpDownTo(0); } //////////////////////////////////////////////////////////////////////////////// SK_DECLARE_STATIC_MUTEX(gMutex); static void create_pool(SkDiscardableMemoryPool** pool) { SkASSERT(NULL == *pool); *pool = SkNEW_ARGS(SkDiscardableMemoryPool, (SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE, &gMutex)); } SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() { static SkDiscardableMemoryPool* gPool(NULL); SK_DECLARE_STATIC_ONCE(create_pool_once); SkOnce(&create_pool_once, create_pool, &gPool); SkASSERT(NULL != gPool); return gPool; } ////////////////////////////////////////////////////////////////////////////////