788c3c467a
Change-Id: I898058f1125f62832f2b2b661c661efb0f84da6d Reviewed-on: https://skia-review.googlesource.com/14274 Commit-Queue: Hal Canary <halcanary@google.com> Reviewed-by: Herb Derby <herb@google.com>
242 lines
7.2 KiB
C++
242 lines
7.2 KiB
C++
/*
|
|
* 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 "SkDiscardableMemory.h"
|
|
#include "SkMakeUnique.h"
|
|
#include "SkMalloc.h"
|
|
#include "SkMutex.h"
|
|
#include "SkTInternalLList.h"
|
|
#include "SkTemplates.h"
|
|
|
|
// Note:
|
|
// A PoolDiscardableMemory is memory that is counted in a pool.
|
|
// A DiscardableMemoryPool is a pool of PoolDiscardableMemorys.
|
|
|
|
namespace {
|
|
|
|
class PoolDiscardableMemory;
|
|
|
|
/**
|
|
* This non-global pool can be used for unit tests to verify that the
|
|
* pool works.
|
|
*/
|
|
class DiscardableMemoryPool : public SkDiscardableMemoryPool {
|
|
public:
|
|
DiscardableMemoryPool(size_t budget);
|
|
~DiscardableMemoryPool() override;
|
|
|
|
std::unique_ptr<SkDiscardableMemory> make(size_t bytes);
|
|
SkDiscardableMemory* create(size_t bytes) override {
|
|
return this->make(bytes).release(); // TODO: change API
|
|
}
|
|
|
|
size_t getRAMUsed() override;
|
|
void setRAMBudget(size_t budget) override;
|
|
size_t getRAMBudget() override { return fBudget; }
|
|
|
|
/** purges all unlocked DMs */
|
|
void dumpPool() override;
|
|
|
|
#if SK_LAZY_CACHE_STATS // Defined in SkDiscardableMemoryPool.h
|
|
int getCacheHits() override { return fCacheHits; }
|
|
int getCacheMisses() override { return fCacheMisses; }
|
|
void resetCacheHitsAndMisses() override {
|
|
fCacheHits = fCacheMisses = 0;
|
|
}
|
|
int fCacheHits;
|
|
int fCacheMisses;
|
|
#endif // SK_LAZY_CACHE_STATS
|
|
|
|
private:
|
|
SkMutex fMutex;
|
|
size_t fBudget;
|
|
size_t fUsed;
|
|
SkTInternalLList<PoolDiscardableMemory> fList;
|
|
|
|
/** Function called to free memory if needed */
|
|
void dumpDownTo(size_t budget);
|
|
/** called by DiscardableMemoryPool upon destruction */
|
|
void removeFromPool(PoolDiscardableMemory* dm);
|
|
/** called by DiscardableMemoryPool::lock() */
|
|
bool lock(PoolDiscardableMemory* dm);
|
|
/** called by DiscardableMemoryPool::unlock() */
|
|
void unlock(PoolDiscardableMemory* dm);
|
|
|
|
friend class PoolDiscardableMemory;
|
|
|
|
typedef SkDiscardableMemory::Factory INHERITED;
|
|
};
|
|
|
|
/**
|
|
* A PoolDiscardableMemory is a SkDiscardableMemory that relies on
|
|
* a DiscardableMemoryPool object to manage the memory.
|
|
*/
|
|
class PoolDiscardableMemory : public SkDiscardableMemory {
|
|
public:
|
|
PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool, SkAutoFree pointer, size_t bytes);
|
|
~PoolDiscardableMemory() override;
|
|
bool lock() override;
|
|
void* data() override;
|
|
void unlock() override;
|
|
friend class DiscardableMemoryPool;
|
|
private:
|
|
SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory);
|
|
sk_sp<DiscardableMemoryPool> fPool;
|
|
bool fLocked;
|
|
SkAutoFree fPointer;
|
|
const size_t fBytes;
|
|
};
|
|
|
|
PoolDiscardableMemory::PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool,
|
|
SkAutoFree pointer,
|
|
size_t bytes)
|
|
: fPool(std::move(pool)), fLocked(true), fPointer(std::move(pointer)), fBytes(bytes) {
|
|
SkASSERT(fPool != nullptr);
|
|
SkASSERT(fPointer != nullptr);
|
|
SkASSERT(fBytes > 0);
|
|
}
|
|
|
|
PoolDiscardableMemory::~PoolDiscardableMemory() {
|
|
SkASSERT(!fLocked); // contract for SkDiscardableMemory
|
|
fPool->removeFromPool(this);
|
|
}
|
|
|
|
bool PoolDiscardableMemory::lock() {
|
|
SkASSERT(!fLocked); // contract for SkDiscardableMemory
|
|
return fPool->lock(this);
|
|
}
|
|
|
|
void* PoolDiscardableMemory::data() {
|
|
SkASSERT(fLocked); // contract for SkDiscardableMemory
|
|
return fPointer.get();
|
|
}
|
|
|
|
void PoolDiscardableMemory::unlock() {
|
|
SkASSERT(fLocked); // contract for SkDiscardableMemory
|
|
fPool->unlock(this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
DiscardableMemoryPool::DiscardableMemoryPool(size_t budget)
|
|
: fBudget(budget)
|
|
, fUsed(0) {
|
|
#if SK_LAZY_CACHE_STATS
|
|
fCacheHits = 0;
|
|
fCacheMisses = 0;
|
|
#endif // SK_LAZY_CACHE_STATS
|
|
}
|
|
DiscardableMemoryPool::~DiscardableMemoryPool() {
|
|
// PoolDiscardableMemory 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 DiscardableMemoryPool::dumpDownTo(size_t budget) {
|
|
fMutex.assertHeld();
|
|
if (fUsed <= budget) {
|
|
return;
|
|
}
|
|
using Iter = SkTInternalLList<PoolDiscardableMemory>::Iter;
|
|
Iter iter;
|
|
PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart);
|
|
while ((fUsed > budget) && (cur)) {
|
|
if (!cur->fLocked) {
|
|
PoolDiscardableMemory* dm = cur;
|
|
SkASSERT(dm->fPointer != nullptr);
|
|
dm->fPointer = nullptr;
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<SkDiscardableMemory> DiscardableMemoryPool::make(size_t bytes) {
|
|
SkAutoFree addr(sk_malloc_flags(bytes, 0));
|
|
if (nullptr == addr) {
|
|
return nullptr;
|
|
}
|
|
auto dm = skstd::make_unique<PoolDiscardableMemory>(sk_ref_sp(this), std::move(addr), bytes);
|
|
SkAutoMutexAcquire autoMutexAcquire(fMutex);
|
|
fList.addToHead(dm.get());
|
|
fUsed += bytes;
|
|
this->dumpDownTo(fBudget);
|
|
return std::move(dm);
|
|
}
|
|
|
|
void DiscardableMemoryPool::removeFromPool(PoolDiscardableMemory* dm) {
|
|
SkAutoMutexAcquire autoMutexAcquire(fMutex);
|
|
// This is called by dm's destructor.
|
|
if (dm->fPointer != nullptr) {
|
|
SkASSERT(fUsed >= dm->fBytes);
|
|
fUsed -= dm->fBytes;
|
|
fList.remove(dm);
|
|
} else {
|
|
SkASSERT(!fList.isInList(dm));
|
|
}
|
|
}
|
|
|
|
bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) {
|
|
SkASSERT(dm != nullptr);
|
|
SkAutoMutexAcquire autoMutexAcquire(fMutex);
|
|
if (nullptr == dm->fPointer) {
|
|
// May have been purged while waiting for lock.
|
|
#if SK_LAZY_CACHE_STATS
|
|
++fCacheMisses;
|
|
#endif // SK_LAZY_CACHE_STATS
|
|
return false;
|
|
}
|
|
dm->fLocked = true;
|
|
fList.remove(dm);
|
|
fList.addToHead(dm);
|
|
#if SK_LAZY_CACHE_STATS
|
|
++fCacheHits;
|
|
#endif // SK_LAZY_CACHE_STATS
|
|
return true;
|
|
}
|
|
|
|
void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) {
|
|
SkASSERT(dm != nullptr);
|
|
SkAutoMutexAcquire autoMutexAcquire(fMutex);
|
|
dm->fLocked = false;
|
|
this->dumpDownTo(fBudget);
|
|
}
|
|
|
|
size_t DiscardableMemoryPool::getRAMUsed() {
|
|
return fUsed;
|
|
}
|
|
void DiscardableMemoryPool::setRAMBudget(size_t budget) {
|
|
SkAutoMutexAcquire autoMutexAcquire(fMutex);
|
|
fBudget = budget;
|
|
this->dumpDownTo(fBudget);
|
|
}
|
|
void DiscardableMemoryPool::dumpPool() {
|
|
SkAutoMutexAcquire autoMutexAcquire(fMutex);
|
|
this->dumpDownTo(0);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
sk_sp<SkDiscardableMemoryPool> SkDiscardableMemoryPool::Make(size_t size) {
|
|
return sk_make_sp<DiscardableMemoryPool>(size);
|
|
}
|
|
|
|
SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() {
|
|
// Intentionally leak this global pool.
|
|
static SkDiscardableMemoryPool* global =
|
|
new DiscardableMemoryPool(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE);
|
|
return global;
|
|
}
|