diff options
-rw-r--r-- | gn/tests.gni | 1 | ||||
-rw-r--r-- | src/core/SkLRUCache.h | 97 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.h | 26 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpuProgramCache.cpp | 144 | ||||
-rw-r--r-- | tests/LRUCacheTest.cpp | 70 |
5 files changed, 188 insertions, 150 deletions
diff --git a/gn/tests.gni b/gn/tests.gni index 8473e0eabe..5f2d8b7931 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -117,6 +117,7 @@ tests_sources = [ "$_tests/LayerDrawLooperTest.cpp", "$_tests/LayerRasterizerTest.cpp", "$_tests/LListTest.cpp", + "$_tests/LRUCacheTest.cpp", "$_tests/MallocPixelRefTest.cpp", "$_tests/MaskCacheTest.cpp", "$_tests/MathTest.cpp", diff --git a/src/core/SkLRUCache.h b/src/core/SkLRUCache.h new file mode 100644 index 0000000000..f7941bb76c --- /dev/null +++ b/src/core/SkLRUCache.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLRUCache_DEFINED +#define SkLRUCache_DEFINED + +#include "SkChecksum.h" +#include "SkTHash.h" +#include "SkTInternalLList.h" + +/** + * A generic LRU cache. + */ +template <typename K, typename V, typename HashK = SkGoodHash> +class SkLRUCache : public SkNoncopyable { +public: + explicit SkLRUCache(int maxCount) + : fMaxCount(maxCount) {} + + ~SkLRUCache() { + Entry* node = fLRU.head(); + while (node) { + fLRU.remove(node); + delete node; + node = fLRU.head(); + } + } + + V* find(const K& key) { + Entry** value = fMap.find(key); + if (!value) { + return nullptr; + } + Entry* entry = *value; + if (entry != fLRU.head()) { + fLRU.remove(entry); + fLRU.addToHead(entry); + } // else it's already at head position, don't need to do anything + return &entry->fValue; + } + + V* insert(const K& key, V value) { + Entry* entry = new Entry(key, std::move(value)); + fMap.set(entry); + fLRU.addToHead(entry); + while (fMap.count() > fMaxCount) { + this->remove(fLRU.tail()->fKey); + } + return &entry->fValue; + } + + int count() { + return fMap.count(); + } + +private: + struct Entry { + Entry(const K& key, V&& value) + : fKey(key) + , fValue(std::move(value)) {} + + K fKey; + V fValue; + + SK_DECLARE_INTERNAL_LLIST_INTERFACE(Entry); + }; + + struct Traits { + static const K& GetKey(Entry* e) { + return e->fKey; + } + + static uint32_t Hash(const K& k) { + return HashK()(k); + } + }; + + void remove(const K& key) { + Entry** value = fMap.find(key); + SkASSERT(value); + Entry* entry = *value; + SkASSERT(key == entry->fKey); + fMap.remove(key); + fLRU.remove(entry); + delete entry; + } + + int fMaxCount; + SkTHashTable<Entry*, K, Traits> fMap; + SkTInternalLList<Entry> fLRU; +}; + +#endif diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h index 0c3d0605c7..c9c3fc4409 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -20,6 +20,7 @@ #include "GrTexturePriv.h" #include "GrWindowRectsState.h" #include "GrXferProcessor.h" +#include "SkLRUCache.h" #include "SkTArray.h" #include "SkTypes.h" @@ -279,29 +280,24 @@ private: bool hasPointSize); private: - enum { - // We may actually have kMaxEntries+1 shaders in the GL context because we create a new - // shader before evicting from the cache. - kMaxEntries = 128, - kHashBits = 6, - }; + // We may actually have kMaxEntries+1 shaders in the GL context because we create a new + // shader before evicting from the cache. + static const int kMaxEntries = 128; struct Entry; - struct ProgDescLess; - // binary search for entry matching desc. returns index into fEntries that matches desc or ~ // of the index of where it should be inserted. int search(const GrProgramDesc& desc) const; - // sorted array of all the entries - Entry* fEntries[kMaxEntries]; - // hash table based on lowest kHashBits bits of the program key. Used to avoid binary - // searching fEntries. - Entry* fHashTable[1 << kHashBits]; + struct DescHash { + uint32_t operator()(const GrProgramDesc& desc) const { + return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0); + } + }; + + SkLRUCache<GrProgramDesc, std::unique_ptr<Entry>, DescHash> fMap; - int fCount; - unsigned int fCurrLRUStamp; GrGLGpu* fGpu; #ifdef PROGRAM_CACHE_STATS int fTotalRequests; diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp index bf6a5980fb..c1ca16096a 100644 --- a/src/gpu/gl/GrGLGpuProgramCache.cpp +++ b/src/gpu/gl/GrGLGpuProgramCache.cpp @@ -23,44 +23,23 @@ static const bool c_DisplayCache{false}; typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; struct GrGLGpu::ProgramCache::Entry { + Entry(sk_sp<GrGLProgram> program) + : fProgram(std::move(program)) {} - Entry() : fProgram(nullptr), fLRUStamp(0) {} - - sk_sp<GrGLProgram> fProgram; - unsigned int fLRUStamp; -}; - -struct GrGLGpu::ProgramCache::ProgDescLess { - bool operator() (const GrProgramDesc& desc, const Entry* entry) { - SkASSERT(entry->fProgram.get()); - return GrProgramDesc::Less(desc, entry->fProgram->getDesc()); - } - - bool operator() (const Entry* entry, const GrProgramDesc& desc) { - SkASSERT(entry->fProgram.get()); - return GrProgramDesc::Less(entry->fProgram->getDesc(), desc); - } + sk_sp<GrGLProgram> fProgram; }; GrGLGpu::ProgramCache::ProgramCache(GrGLGpu* gpu) - : fCount(0) - , fCurrLRUStamp(0) + : fMap(kMaxEntries) , fGpu(gpu) #ifdef PROGRAM_CACHE_STATS , fTotalRequests(0) , fCacheMisses(0) , fHashMisses(0) #endif -{ - for (int i = 0; i < 1 << kHashBits; ++i) { - fHashTable[i] = nullptr; - } -} +{} GrGLGpu::ProgramCache::~ProgramCache() { - for (int i = 0; i < fCount; ++i){ - delete fEntries[i]; - } // dump stats #ifdef PROGRAM_CACHE_STATS if (c_DisplayCache) { @@ -78,20 +57,6 @@ GrGLGpu::ProgramCache::~ProgramCache() { } void GrGLGpu::ProgramCache::abandon() { - for (int i = 0; i < fCount; ++i) { - SkASSERT(fEntries[i]->fProgram.get()); - fEntries[i]->fProgram->abandon(); - delete fEntries[i]; - fEntries[i] = nullptr; - } - fCount = 0; - - // zero out hash table - for (int i = 0; i < 1 << kHashBits; i++) { - fHashTable[i] = nullptr; - } - - fCurrLRUStamp = 0; #ifdef PROGRAM_CACHE_STATS fTotalRequests = 0; fCacheMisses = 0; @@ -99,11 +64,6 @@ void GrGLGpu::ProgramCache::abandon() { #endif } -int GrGLGpu::ProgramCache::search(const GrProgramDesc& desc) const { - ProgDescLess less; - return SkTSearch(fEntries, fCount, desc, sizeof(Entry*), less); -} - GrGLProgram* GrGLGpu::ProgramCache::refProgram(const GrGLGpu* gpu, const GrPipeline& pipeline, const GrPrimitiveProcessor& primProc, @@ -119,33 +79,8 @@ GrGLProgram* GrGLGpu::ProgramCache::refProgram(const GrGLGpu* gpu, return nullptr; } desc.finalize(); - - Entry* entry = nullptr; - - uint32_t hashIdx = desc.getChecksum(); - hashIdx ^= hashIdx >> 16; - if (kHashBits <= 8) { - hashIdx ^= hashIdx >> 8; - } - hashIdx &=((1 << kHashBits) - 1); - Entry* hashedEntry = fHashTable[hashIdx]; - if (hashedEntry && hashedEntry->fProgram->getDesc() == desc) { - SkASSERT(hashedEntry->fProgram); - entry = hashedEntry; - } - - int entryIdx; - if (nullptr == entry) { - entryIdx = this->search(desc); - if (entryIdx >= 0) { - entry = fEntries[entryIdx]; -#ifdef PROGRAM_CACHE_STATS - ++fHashMisses; -#endif - } - } - - if (nullptr == entry) { + std::unique_ptr<Entry>* entry = fMap.find(desc); + if (!entry) { // We have a cache miss #ifdef PROGRAM_CACHE_STATS ++fCacheMisses; @@ -154,69 +89,8 @@ GrGLProgram* GrGLGpu::ProgramCache::refProgram(const GrGLGpu* gpu, if (nullptr == program) { return nullptr; } - int purgeIdx = 0; - if (fCount < kMaxEntries) { - entry = new Entry; - purgeIdx = fCount++; - fEntries[purgeIdx] = entry; - } else { - SkASSERT(fCount == kMaxEntries); - purgeIdx = 0; - for (int i = 1; i < kMaxEntries; ++i) { - if (fEntries[i]->fLRUStamp < fEntries[purgeIdx]->fLRUStamp) { - purgeIdx = i; - } - } - entry = fEntries[purgeIdx]; - int purgedHashIdx = entry->fProgram->getDesc().getChecksum() & ((1 << kHashBits) - 1); - if (fHashTable[purgedHashIdx] == entry) { - fHashTable[purgedHashIdx] = nullptr; - } - } - SkASSERT(fEntries[purgeIdx] == entry); - entry->fProgram.reset(program); - // We need to shift fEntries around so that the entry currently at purgeIdx is placed - // just before the entry at ~entryIdx (in order to keep fEntries sorted by descriptor). - entryIdx = ~entryIdx; - if (entryIdx < purgeIdx) { - // Let E and P be the entries at index entryIdx and purgeIdx, respectively. - // If the entries array looks like this: - // aaaaEbbbbbPccccc - // we rearrange it to look like this: - // aaaaPEbbbbbccccc - size_t copySize = (purgeIdx - entryIdx) * sizeof(Entry*); - memmove(fEntries + entryIdx + 1, fEntries + entryIdx, copySize); - fEntries[entryIdx] = entry; - } else if (purgeIdx < entryIdx) { - // If the entries array looks like this: - // aaaaPbbbbbEccccc - // we rearrange it to look like this: - // aaaabbbbbPEccccc - size_t copySize = (entryIdx - purgeIdx - 1) * sizeof(Entry*); - memmove(fEntries + purgeIdx, fEntries + purgeIdx + 1, copySize); - fEntries[entryIdx - 1] = entry; - } -#ifdef SK_DEBUG - SkASSERT(fEntries[0]->fProgram.get()); - for (int i = 0; i < fCount - 1; ++i) { - SkASSERT(fEntries[i + 1]->fProgram.get()); - const GrProgramDesc& a = fEntries[i]->fProgram->getDesc(); - const GrProgramDesc& b = fEntries[i + 1]->fProgram->getDesc(); - SkASSERT(GrProgramDesc::Less(a, b)); - SkASSERT(!GrProgramDesc::Less(b, a)); - } -#endif + entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(sk_sp<GrGLProgram>(program)))); } - fHashTable[hashIdx] = entry; - entry->fLRUStamp = fCurrLRUStamp; - - if (SK_MaxU32 == fCurrLRUStamp) { - // wrap around! just trash our LRU, one time hit. - for (int i = 0; i < fCount; ++i) { - fEntries[i]->fLRUStamp = 0; - } - } - ++fCurrLRUStamp; - return SkRef(entry->fProgram.get()); + return SkRef((*entry)->fProgram.get()); } diff --git a/tests/LRUCacheTest.cpp b/tests/LRUCacheTest.cpp new file mode 100644 index 0000000000..6a65e4ac2d --- /dev/null +++ b/tests/LRUCacheTest.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkLRUCache.h" +#include "Test.h" + +struct Value { + Value(int value, int* counter) + : fValue(value) + , fCounter(counter) { + (*fCounter)++; + } + + ~Value() { + (*fCounter)--; + } + + int fValue; + int* fCounter; +}; + +DEF_TEST(LRUCacheSequential, r) { + int instances = 0; + { + static const int kSize = 100; + SkLRUCache<int, std::unique_ptr<Value>> test(kSize); + for (int i = 1; i < kSize * 2; i++) { + REPORTER_ASSERT(r, !test.find(i)); + test.insert(i, std::unique_ptr<Value>(new Value(i * i, &instances))); + REPORTER_ASSERT(r, test.find(i)); + REPORTER_ASSERT(r, i * i == (*test.find(i))->fValue); + if (i > kSize) { + REPORTER_ASSERT(r, kSize == instances); + REPORTER_ASSERT(r, !test.find(i - kSize)); + } else { + REPORTER_ASSERT(r, i == instances); + } + REPORTER_ASSERT(r, (int) test.count() == instances); + } + } + REPORTER_ASSERT(r, 0 == instances); +} + +DEF_TEST(LRUCacheRandom, r) { + int instances = 0; + { + int seq[] = { 0, 1, 2, 3, 4, 1, 6, 2, 7, 5, 3, 2, 2, 3, 1, 7 }; + int expected[] = { 7, 1, 3, 2, 5 }; + static const int kSize = 5; + SkLRUCache<int, std::unique_ptr<Value>> test(kSize); + for (int i = 0; i < (int) (sizeof(seq) / sizeof(int)); i++) { + int k = seq[i]; + if (!test.find(k)) { + test.insert(k, std::unique_ptr<Value>(new Value(k, &instances))); + } + } + REPORTER_ASSERT(r, kSize == instances); + REPORTER_ASSERT(r, kSize == test.count()); + for (int i = 0; i < kSize; i++) { + int k = expected[i]; + REPORTER_ASSERT(r, test.find(k)); + REPORTER_ASSERT(r, k == (*test.find(k))->fValue); + } + } + REPORTER_ASSERT(r, 0 == instances); +} |