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:
bsalomon 2016-10-03 14:07:01 -07:00 committed by Commit bot
parent e60d85597f
commit 0f147ac2ae
5 changed files with 173 additions and 37 deletions

View File

@ -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);
}

View File

@ -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();

View File

@ -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;
};

View File

@ -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 {

View File

@ -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