Make GrResourceCache dynamically change between LRU and random replacement strategies.
Random performs significantly better when each frame exceeds the budget by a small margin whereas LRU has worst case behavior. The decision of which to use is made based on the history from a few frames of the ratio of total unique key cache misses to unique key cache misses of resources purged in the last 2 frames. GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2321563006 Review-Url: https://codereview.chromium.org/2321563006
This commit is contained in:
parent
e60d85597f
commit
0f147ac2ae
@ -58,6 +58,7 @@ public:
|
||||
this->validate();
|
||||
|
||||
if (!(--fRefCnt)) {
|
||||
SkASSERT(fRefCnt >= 0);
|
||||
if (!static_cast<const DERIVED*>(this)->notifyRefCountIsZero()) {
|
||||
return;
|
||||
}
|
||||
@ -117,6 +118,7 @@ private:
|
||||
|
||||
private:
|
||||
void didRemoveRefOrPendingIO(CntType cntTypeRemoved) const {
|
||||
this->validate();
|
||||
if (0 == fPendingReads && 0 == fPendingWrites && 0 == fRefCnt) {
|
||||
static_cast<const DERIVED*>(this)->notifyAllCntsAreZero(cntTypeRemoved);
|
||||
}
|
||||
|
@ -129,10 +129,17 @@ void GrDrawingManager::internalFlush(GrResourceCache::FlushType type) {
|
||||
#endif
|
||||
|
||||
fFlushState.reset();
|
||||
// We always have to notify the cache when it requested a flush so it can reset its state.
|
||||
if (flushed || type == GrResourceCache::FlushType::kCacheRequested) {
|
||||
// Avoid notifying the cache about successive client flushes where no rendering occurred between
|
||||
// them.
|
||||
bool skipNotify = false;
|
||||
if (!flushed) {
|
||||
skipNotify = GrResourceCache::kExternal == fLastFlushType &&
|
||||
GrResourceCache::kExternal == type;
|
||||
}
|
||||
if (!skipNotify) {
|
||||
fContext->getResourceCache()->notifyFlushOccurred(type);
|
||||
}
|
||||
fLastFlushType = type;
|
||||
fFlushing = false;
|
||||
}
|
||||
|
||||
@ -145,6 +152,9 @@ void GrDrawingManager::prepareSurfaceForExternalIO(GrSurface* surface) {
|
||||
|
||||
if (surface->surfacePriv().hasPendingIO()) {
|
||||
this->flush();
|
||||
} else if (GrResourceCache::kExternal != fLastFlushType) {
|
||||
fContext->getResourceCache()->notifyFlushOccurred(GrResourceCache::kExternal);
|
||||
fLastFlushType = GrResourceCache::kExternal;
|
||||
}
|
||||
|
||||
GrRenderTarget* rt = surface->asRenderTarget();
|
||||
|
@ -76,6 +76,7 @@ private:
|
||||
, fSoftwarePathRenderer(nullptr)
|
||||
, fFlushState(context->getGpu(), context->resourceProvider())
|
||||
, fFlushing(false)
|
||||
, fLastFlushType(GrResourceCache::FlushType::kExternal)
|
||||
, fIsImmediateMode(isImmediateMode) {
|
||||
}
|
||||
|
||||
@ -107,6 +108,7 @@ private:
|
||||
|
||||
GrBatchFlushState fFlushState;
|
||||
bool fFlushing;
|
||||
GrResourceCache::FlushType fLastFlushType;
|
||||
|
||||
bool fIsImmediateMode;
|
||||
};
|
||||
|
@ -57,13 +57,22 @@ private:
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
constexpr int GrResourceCache::kStrategyScoreMin;
|
||||
constexpr int GrResourceCache::kStrategyScoreMax;
|
||||
constexpr int GrResourceCache::kInitialStrategyScore;
|
||||
|
||||
GrResourceCache::GrResourceCache(const GrCaps* caps)
|
||||
: fTimestamp(0)
|
||||
, fMaxCount(kDefaultMaxCount)
|
||||
, fMaxBytes(kDefaultMaxSize)
|
||||
, fMaxUnusedFlushes(kDefaultMaxUnusedFlushes)
|
||||
, fStrategy(ReplacementStrategy::kLRU)
|
||||
, fStrategyScore(kInitialStrategyScore)
|
||||
, fTotalMissesThisFlush(0)
|
||||
, fMissesThisFlushPurgedRecently(0)
|
||||
, fUniqueKeysPurgedThisFlushStorage {new SkChunkAlloc(8*sizeof(GrUniqueKey)),
|
||||
new SkChunkAlloc(8*sizeof(GrUniqueKey))}
|
||||
, fFlushParity(0)
|
||||
#if GR_CACHE_STATS
|
||||
, fHighWaterCount(0)
|
||||
, fHighWaterBytes(0)
|
||||
@ -73,8 +82,8 @@ GrResourceCache::GrResourceCache(const GrCaps* caps)
|
||||
, fBytes(0)
|
||||
, fBudgetedCount(0)
|
||||
, fBudgetedBytes(0)
|
||||
, fRequestFlush(false)
|
||||
, fExternalFlushCnt(0)
|
||||
, fIsPurging(false)
|
||||
, fPreferVRAMUseOverFlushes(caps->preferVRAMUseOverFlushes()) {
|
||||
SkDEBUGCODE(fCount = 0;)
|
||||
SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr;)
|
||||
@ -82,6 +91,8 @@ GrResourceCache::GrResourceCache(const GrCaps* caps)
|
||||
|
||||
GrResourceCache::~GrResourceCache() {
|
||||
this->releaseAll();
|
||||
delete fUniqueKeysPurgedThisFlushStorage[0];
|
||||
delete fUniqueKeysPurgedThisFlushStorage[1];
|
||||
}
|
||||
|
||||
void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
|
||||
@ -227,8 +238,10 @@ private:
|
||||
GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey,
|
||||
size_t resourceSize,
|
||||
uint32_t flags) {
|
||||
// We don't currently track misses for scratch resources for selecting the replacement policy.
|
||||
// The reason is that it is common to look for a scratch resource before creating a texture
|
||||
// that will immediately become uniquely keyed.
|
||||
SkASSERT(scratchKey.isValid());
|
||||
|
||||
GrGpuResource* resource;
|
||||
if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
|
||||
resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
|
||||
@ -256,6 +269,25 @@ GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& sc
|
||||
return resource;
|
||||
}
|
||||
|
||||
GrGpuResource* GrResourceCache::findAndRefUniqueResource(const GrUniqueKey& key) {
|
||||
GrGpuResource* resource = fUniqueHash.find(key);
|
||||
if (resource) {
|
||||
this->refAndMakeResourceMRU(resource);
|
||||
} else {
|
||||
this->recordKeyMiss(key);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
void GrResourceCache::recordKeyMiss(const GrUniqueKey& key) {
|
||||
// If a resource with this key was purged either this flush or the previous flush, consider it
|
||||
// a recent purge.
|
||||
if (fUniqueKeysPurgedThisFlush[0].find(key) || fUniqueKeysPurgedThisFlush[1].find(key)) {
|
||||
++fMissesThisFlushPurgedRecently;
|
||||
}
|
||||
++fTotalMissesThisFlush;
|
||||
}
|
||||
|
||||
void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
|
||||
SkASSERT(resource->resourcePriv().getScratchKey().isValid());
|
||||
if (!resource->getUniqueKey().isValid()) {
|
||||
@ -380,9 +412,12 @@ void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t fla
|
||||
} else {
|
||||
// Purge the resource immediately if we're over budget
|
||||
// Also purge if the resource has neither a valid scratch key nor a unique key.
|
||||
bool noKey = !resource->resourcePriv().getScratchKey().isValid() &&
|
||||
!resource->getUniqueKey().isValid();
|
||||
if (!this->overBudget() && !noKey) {
|
||||
bool hasKey = resource->resourcePriv().getScratchKey().isValid() ||
|
||||
resource->getUniqueKey().isValid();
|
||||
if (hasKey) {
|
||||
if (this->overBudget()) {
|
||||
this->purgeAsNeeded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -442,7 +477,36 @@ void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
|
||||
this->validate();
|
||||
}
|
||||
|
||||
void GrResourceCache::purgeAsNeeded() {
|
||||
void GrResourceCache::recordPurgedKey(GrGpuResource* resource) {
|
||||
// This maximum exists to avoid allocating too much space for key tracking.
|
||||
static constexpr int kMaxTrackedKeys = 256;
|
||||
if (fUniqueKeysPurgedThisFlush[fFlushParity].count() >= kMaxTrackedKeys) {
|
||||
return;
|
||||
}
|
||||
if (resource->getUniqueKey().isValid() &&
|
||||
!fUniqueKeysPurgedThisFlush[fFlushParity].find(resource->getUniqueKey())) {
|
||||
void* p = fUniqueKeysPurgedThisFlushStorage[fFlushParity]->allocThrow(sizeof(GrUniqueKey));
|
||||
GrUniqueKey* copy = new (p) GrUniqueKey;
|
||||
*copy = resource->getUniqueKey();
|
||||
fUniqueKeysPurgedThisFlush[fFlushParity].add(copy);
|
||||
}
|
||||
}
|
||||
|
||||
GrGpuResource* GrResourceCache::selectResourceUsingStrategy() {
|
||||
switch (fStrategy) {
|
||||
case ReplacementStrategy::kLRU:
|
||||
return fPurgeableQueue.peek();
|
||||
case ReplacementStrategy::kRandom:
|
||||
return fPurgeableQueue.at(fRandom.nextULessThan(fPurgeableQueue.count()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GrResourceCache::internalPurgeAsNeeded(bool fromFlushNotification) {
|
||||
if (fIsPurging) {
|
||||
return;
|
||||
}
|
||||
fIsPurging = true;
|
||||
SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
|
||||
fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
|
||||
if (invalidKeyMsgs.count()) {
|
||||
@ -470,26 +534,31 @@ void GrResourceCache::purgeAsNeeded() {
|
||||
}
|
||||
GrGpuResource* resource = fPurgeableQueue.peek();
|
||||
SkASSERT(resource->isPurgeable());
|
||||
this->recordPurgedKey(resource);
|
||||
resource->cacheAccess().release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ReplacementStrategy::kRandom == fStrategy && !fromFlushNotification) {
|
||||
// Wait until after the requested flush when all the pending IO resources will be eligible
|
||||
// for the draft.
|
||||
SkASSERT(!this->overBudget() || this->requestsFlush());
|
||||
fIsPurging = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool stillOverbudget = this->overBudget();
|
||||
while (stillOverbudget && fPurgeableQueue.count()) {
|
||||
GrGpuResource* resource = fPurgeableQueue.peek();
|
||||
GrGpuResource* resource = this->selectResourceUsingStrategy();
|
||||
SkASSERT(resource->isPurgeable());
|
||||
this->recordPurgedKey(resource);
|
||||
resource->cacheAccess().release();
|
||||
stillOverbudget = this->overBudget();
|
||||
}
|
||||
|
||||
this->validate();
|
||||
|
||||
if (stillOverbudget) {
|
||||
// Set this so that GrDrawingManager will issue a flush to free up resources with pending
|
||||
// IO that we were unable to purge in this pass.
|
||||
fRequestFlush = true;
|
||||
}
|
||||
fIsPurging = false;
|
||||
}
|
||||
|
||||
void GrResourceCache::purgeAllUnlocked() {
|
||||
@ -549,7 +618,6 @@ uint32_t GrResourceCache::getNextTimestamp() {
|
||||
*sortedPurgeableResources.append() = fPurgeableQueue.peek();
|
||||
fPurgeableQueue.pop();
|
||||
}
|
||||
|
||||
SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1,
|
||||
CompareTimestamp);
|
||||
|
||||
@ -600,10 +668,25 @@ void GrResourceCache::notifyFlushOccurred(FlushType type) {
|
||||
case FlushType::kImmediateMode:
|
||||
break;
|
||||
case FlushType::kCacheRequested:
|
||||
SkASSERT(fRequestFlush);
|
||||
fRequestFlush = false;
|
||||
break;
|
||||
case FlushType::kExternal:
|
||||
case FlushType::kExternal: {
|
||||
int scoreDelta = 1;
|
||||
if (fMissesThisFlushPurgedRecently) {
|
||||
// If > 60% of our cache misses were things we purged in the last two flushes
|
||||
// then we move closer towards selecting random replacement.
|
||||
if ((float)fMissesThisFlushPurgedRecently / fTotalMissesThisFlush > 0.6f) {
|
||||
scoreDelta = -1;
|
||||
}
|
||||
}
|
||||
fStrategyScore = SkTPin(fStrategyScore + scoreDelta, kStrategyScoreMin,
|
||||
kStrategyScoreMax);
|
||||
fStrategy = fStrategyScore < 0 ? ReplacementStrategy::kRandom
|
||||
: ReplacementStrategy::kLRU;
|
||||
fMissesThisFlushPurgedRecently = 0;
|
||||
fTotalMissesThisFlush = 0;
|
||||
fFlushParity = -(fFlushParity - 1);
|
||||
fUniqueKeysPurgedThisFlush[fFlushParity].reset();
|
||||
fUniqueKeysPurgedThisFlushStorage[fFlushParity]->rewind();
|
||||
++fExternalFlushCnt;
|
||||
if (0 == fExternalFlushCnt) {
|
||||
// When this wraps just reset all the purgeable resources' last used flush state.
|
||||
@ -612,8 +695,9 @@ void GrResourceCache::notifyFlushOccurred(FlushType type) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->purgeAsNeeded();
|
||||
this->internalPurgeAsNeeded(true);
|
||||
}
|
||||
|
||||
void GrResourceCache::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
|
||||
|
@ -13,7 +13,9 @@
|
||||
#include "GrGpuResourcePriv.h"
|
||||
#include "GrResourceCache.h"
|
||||
#include "GrResourceKey.h"
|
||||
#include "SkChunkAlloc.h"
|
||||
#include "SkMessageBus.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkRefCnt.h"
|
||||
#include "SkTArray.h"
|
||||
#include "SkTDPQueue.h"
|
||||
@ -137,13 +139,7 @@ public:
|
||||
/**
|
||||
* Find a resource that matches a unique key.
|
||||
*/
|
||||
GrGpuResource* findAndRefUniqueResource(const GrUniqueKey& key) {
|
||||
GrGpuResource* resource = fUniqueHash.find(key);
|
||||
if (resource) {
|
||||
this->refAndMakeResourceMRU(resource);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
GrGpuResource* findAndRefUniqueResource(const GrUniqueKey& key);
|
||||
|
||||
/**
|
||||
* Query whether a unique key exists in the cache.
|
||||
@ -154,14 +150,19 @@ public:
|
||||
|
||||
/** Purges resources to become under budget and processes resources with invalidated unique
|
||||
keys. */
|
||||
void purgeAsNeeded();
|
||||
void purgeAsNeeded() { this->internalPurgeAsNeeded(false); }
|
||||
|
||||
/** Purges all resources that don't have external owners. */
|
||||
void purgeAllUnlocked();
|
||||
|
||||
/** Returns true if the cache would like a flush to occur in order to make more resources
|
||||
purgeable. */
|
||||
bool requestsFlush() const { return fRequestFlush; }
|
||||
bool requestsFlush() const {
|
||||
// When in random replacement mode we request a flush in order to make as many resources
|
||||
// as possible subject to replacement.
|
||||
return this->overBudget() && (ReplacementStrategy::kRandom == fStrategy ||
|
||||
0 == fPurgeableQueue.count());
|
||||
}
|
||||
|
||||
enum FlushType {
|
||||
kExternal,
|
||||
@ -232,10 +233,14 @@ private:
|
||||
void refAndMakeResourceMRU(GrGpuResource*);
|
||||
/// @}
|
||||
|
||||
void internalPurgeAsNeeded(bool fromFlushNotification);
|
||||
void processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage>&);
|
||||
void addToNonpurgeableArray(GrGpuResource*);
|
||||
void removeFromNonpurgeableArray(GrGpuResource*);
|
||||
bool overBudget() const { return fBudgetedBytes > fMaxBytes || fBudgetedCount > fMaxCount; }
|
||||
GrGpuResource* selectResourceUsingStrategy();
|
||||
void recordPurgedKey(GrGpuResource*);
|
||||
void recordKeyMiss(const GrUniqueKey&);
|
||||
|
||||
bool wouldFit(size_t bytes) {
|
||||
return fBudgetedBytes+bytes <= fMaxBytes && fBudgetedCount+1 <= fMaxCount;
|
||||
@ -254,22 +259,27 @@ private:
|
||||
|
||||
class AvailableForScratchUse;
|
||||
|
||||
struct ScratchMapTraits {
|
||||
struct HashTraitsBase {
|
||||
static uint32_t Hash(const GrResourceKey& key) { return key.hash(); }
|
||||
};
|
||||
|
||||
struct ScratchMapTraits : public HashTraitsBase {
|
||||
static const GrScratchKey& GetKey(const GrGpuResource& r) {
|
||||
return r.resourcePriv().getScratchKey();
|
||||
}
|
||||
|
||||
static uint32_t Hash(const GrScratchKey& key) { return key.hash(); }
|
||||
};
|
||||
typedef SkTMultiMap<GrGpuResource, GrScratchKey, ScratchMapTraits> ScratchMap;
|
||||
|
||||
struct UniqueHashTraits {
|
||||
struct UniqueHashTraits : public HashTraitsBase {
|
||||
static const GrUniqueKey& GetKey(const GrGpuResource& r) { return r.getUniqueKey(); }
|
||||
|
||||
static uint32_t Hash(const GrUniqueKey& key) { return key.hash(); }
|
||||
};
|
||||
typedef SkTDynamicHash<GrGpuResource, GrUniqueKey, UniqueHashTraits> UniqueHash;
|
||||
|
||||
struct UniqueSetTraits : public HashTraitsBase {
|
||||
static const GrUniqueKey& GetKey(const GrUniqueKey& key) { return key; }
|
||||
};
|
||||
typedef SkTDynamicHash<GrUniqueKey, GrUniqueKey, UniqueSetTraits> UniqueKeySet;
|
||||
|
||||
static bool CompareTimestamp(GrGpuResource* const& a, GrGpuResource* const& b) {
|
||||
return a->cacheAccess().timestamp() < b->cacheAccess().timestamp();
|
||||
}
|
||||
@ -278,6 +288,22 @@ private:
|
||||
return res->cacheAccess().accessCacheIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* The resource cache chooses one of these replacement strategies based on a "strategy score"
|
||||
* updated after each external flush based on unique key cache misses.
|
||||
*/
|
||||
enum class ReplacementStrategy {
|
||||
kLRU,
|
||||
kRandom
|
||||
};
|
||||
/**
|
||||
* When the current strategy score is >=0 LRU is chosen, when it is < 0 random is chosen. The
|
||||
* absolute value of the score moves by 1 each flush.
|
||||
*/
|
||||
static constexpr int kStrategyScoreMin = -5;
|
||||
static constexpr int kStrategyScoreMax = 4;
|
||||
static constexpr int kInitialStrategyScore = 2;
|
||||
|
||||
typedef SkMessageBus<GrUniqueKeyInvalidatedMessage>::Inbox InvalidUniqueKeyInbox;
|
||||
typedef SkTDPQueue<GrGpuResource*, CompareTimestamp, AccessResourceIndex> PurgeableQueue;
|
||||
typedef SkTDArray<GrGpuResource*> ResourceArray;
|
||||
@ -299,6 +325,17 @@ private:
|
||||
size_t fMaxBytes;
|
||||
int fMaxUnusedFlushes;
|
||||
|
||||
// Data related to replacement strategy.
|
||||
SkRandom fRandom;
|
||||
ReplacementStrategy fStrategy;
|
||||
int fStrategyScore;
|
||||
int fTotalMissesThisFlush;
|
||||
int fMissesThisFlushPurgedRecently;
|
||||
UniqueKeySet fUniqueKeysPurgedThisFlush[2];
|
||||
// These are pointers to SkChunckAlloc because of gcc bug 63707
|
||||
SkChunkAlloc* fUniqueKeysPurgedThisFlushStorage[2];
|
||||
int fFlushParity;
|
||||
|
||||
#if GR_CACHE_STATS
|
||||
int fHighWaterCount;
|
||||
size_t fHighWaterBytes;
|
||||
@ -314,9 +351,10 @@ private:
|
||||
int fBudgetedCount;
|
||||
size_t fBudgetedBytes;
|
||||
|
||||
bool fRequestFlush;
|
||||
uint32_t fExternalFlushCnt;
|
||||
|
||||
bool fIsPurging;
|
||||
|
||||
InvalidUniqueKeyInbox fInvalidUniqueKeyInbox;
|
||||
|
||||
// This resource is allowed to be in the nonpurgeable array for the sake of validate() because
|
||||
|
Loading…
Reference in New Issue
Block a user