forked from mirrors/gecko-dev
		
	 4e67253eee
			
		
	
	
		4e67253eee
		
	
	
	
	
		
			
			A small LRU cache (i.e., evicting Least Recenty Used items) that's most optimized for most recently used items. Differential Revision: https://phabricator.services.mozilla.com/D82302
		
			
				
	
	
		
			199 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #ifndef SmallArrayLRUCache_h
 | |
| #define SmallArrayLRUCache_h
 | |
| 
 | |
| #include "mozilla/Mutex.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <type_traits>
 | |
| #include <utility>
 | |
| 
 | |
| // Uncomment the next line to get shutdown stats about cache usage.
 | |
| // #define SMALLARRAYLRUCACHE_STATS
 | |
| 
 | |
| #ifdef SMALLARRAYLRUCACHE_STATS
 | |
| #  include <cstdio>
 | |
| #endif
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| // Array-based LRU cache, thread-safe.
 | |
| // Optimized for cases where `FetchOrAdd` is used with the same key most
 | |
| // recently, and assuming the cost of running the value-builder function is much
 | |
| // more expensive than going through the whole list.
 | |
| // Note: No time limits on keeping items.
 | |
| // TODO: Move to more public place, if this could be used elsewhere; make sure
 | |
| // the cost/benefits are highlighted.
 | |
| template <typename Key, typename Value, unsigned LRUCapacity>
 | |
| class SmallArrayLRUCache {
 | |
|  public:
 | |
|   static_assert(std::is_default_constructible_v<Key>);
 | |
|   static_assert(std::is_trivially_constructible_v<Key>);
 | |
|   static_assert(std::is_trivially_copyable_v<Key>);
 | |
|   static_assert(std::is_default_constructible_v<Value>);
 | |
|   static_assert(LRUCapacity >= 2);
 | |
|   static_assert(LRUCapacity <= 1024,
 | |
|                 "This seems a bit big, is this the right cache for your use?");
 | |
| 
 | |
|   // Reset all stored values to their default, and set cache size to zero.
 | |
|   void Clear() {
 | |
|     mozilla::OffTheBooksMutexAutoLock lock(mMutex);
 | |
|     if (mSize == ShutdownSize) {
 | |
|       return;
 | |
|     }
 | |
|     Clear(lock);
 | |
|   }
 | |
| 
 | |
|   // Clear the cache, and then prevent further uses (other functions will do
 | |
|   // nothing).
 | |
|   void Shutdown() {
 | |
|     mozilla::OffTheBooksMutexAutoLock lock(mMutex);
 | |
|     if (mSize == ShutdownSize) {
 | |
|       return;
 | |
|     }
 | |
|     Clear(lock);
 | |
|     mSize = ShutdownSize;
 | |
|   }
 | |
| 
 | |
|   // Add a key-value.
 | |
|   template <typename ToValue>
 | |
|   void Add(Key aKey, ToValue&& aValue) {
 | |
|     mozilla::OffTheBooksMutexAutoLock lock(mMutex);
 | |
| 
 | |
|     if (mSize == ShutdownSize) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Quick add to the front, don't remove possible duplicate handles later in
 | |
|     // the list, they will eventually drop off the end.
 | |
|     KeyAndValue* const item0 = &mLRUArray[0];
 | |
|     mSize = std::min(mSize + 1, LRUCapacity);
 | |
|     if (MOZ_LIKELY(mSize != 1)) {
 | |
|       // List is not empty.
 | |
|       // Make a hole at the start.
 | |
|       std::move_backward(item0, item0 + mSize - 1, item0 + mSize);
 | |
|     }
 | |
|     item0->mKey = aKey;
 | |
|     item0->mValue = std::forward<ToValue>(aValue);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Look for the value associated with `aKey` in the cache.
 | |
|   // If not found, run `aValueFunction()`, add it in the cache before returning.
 | |
|   // After shutdown, just run `aValueFunction()`.
 | |
|   template <typename ValueFunction>
 | |
|   Value FetchOrAdd(Key aKey, ValueFunction&& aValueFunction) {
 | |
|     Value value;
 | |
|     mozilla::OffTheBooksMutexAutoLock lock(mMutex);
 | |
| 
 | |
|     if (mSize == ShutdownSize) {
 | |
|       value = std::forward<ValueFunction>(aValueFunction)();
 | |
|       return value;
 | |
|     }
 | |
| 
 | |
|     KeyAndValue* const item0 = &mLRUArray[0];
 | |
|     if (MOZ_UNLIKELY(mSize == 0)) {
 | |
|       // List is empty.
 | |
|       value = std::forward<ValueFunction>(aValueFunction)();
 | |
|       item0->mKey = aKey;
 | |
|       item0->mValue = value;
 | |
|       mSize = 1;
 | |
|       return value;
 | |
|     }
 | |
| 
 | |
|     if (MOZ_LIKELY(item0->mKey == aKey)) {
 | |
|       // This is already at the beginning of the list, we're done.
 | |
| #ifdef SMALLARRAYLRUCACHE_STATS
 | |
|       ++mCacheFoundAt[0];
 | |
| #endif  // SMALLARRAYLRUCACHE_STATS
 | |
|       value = item0->mValue;
 | |
|       return value;
 | |
|     }
 | |
| 
 | |
|     for (KeyAndValue* item = item0 + 1; item != item0 + mSize; ++item) {
 | |
|       if (item->mKey == aKey) {
 | |
|         // Found handle in the middle.
 | |
| #ifdef SMALLARRAYLRUCACHE_STATS
 | |
|         ++mCacheFoundAt[unsigned(item - item0)];
 | |
| #endif  // SMALLARRAYLRUCACHE_STATS
 | |
|         value = item->mValue;
 | |
|         // Move this item to the start of the list.
 | |
|         std::rotate(item0, item, item + 1);
 | |
|         return value;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Handle was not in the list.
 | |
| #ifdef SMALLARRAYLRUCACHE_STATS
 | |
|     ++mCacheFoundAt[LRUCapacity];
 | |
| #endif  // SMALLARRAYLRUCACHE_STATS
 | |
|     {
 | |
|       // Don't lock while doing the potentially-expensive ValueFunction().
 | |
|       // This means that the list could change when we lock again, but
 | |
|       // it's okay because we'll want to add the new entry at the beginning
 | |
|       // anyway, whatever else is in the list then.
 | |
|       // In the worst case, it could be the same handle as another `FetchOrAdd`
 | |
|       // in parallel, it just means it will be duplicated, so it's a little bit
 | |
|       // less efficient (using the extra space), but not wrong (the next
 | |
|       // `FetchOrAdd` will find the first one).
 | |
|       mozilla::OffTheBooksMutexAutoUnlock unlock(mMutex);
 | |
|       value = std::forward<ValueFunction>(aValueFunction)();
 | |
|     }
 | |
|     // Make a hole at the start, and put the value there.
 | |
|     mSize = std::min(mSize + 1, LRUCapacity);
 | |
|     std::move_backward(item0, item0 + mSize - 1, item0 + mSize);
 | |
|     item0->mKey = aKey;
 | |
|     item0->mValue = value;
 | |
|     return value;
 | |
|   }
 | |
| 
 | |
| #ifdef SMALLARRAYLRUCACHE_STATS
 | |
|   ~SmallArrayLRUCache() {
 | |
|     if (mSize != 0 && mSize != ShutdownSize) {
 | |
|       fprintf(stderr,
 | |
|               "***** SmallArrayLRUCache stats: (position -> hit count)\n");
 | |
|       for (unsigned i = 0; i < mSize; ++i) {
 | |
|         fprintf(stderr, "***** %3u -> %6u\n", i, mCacheFoundAt[i]);
 | |
|       }
 | |
|       fprintf(stderr, "***** not found -> %6u\n", mCacheFoundAt[LRUCapacity]);
 | |
|     }
 | |
|   }
 | |
| #endif  // SMALLARRAYLRUCACHE_STATS
 | |
| 
 | |
|  private:
 | |
|   void Clear(const mozilla::OffTheBooksMutexAutoLock&) {
 | |
|     for (KeyAndValue* item = &mLRUArray[0]; item != &mLRUArray[mSize]; ++item) {
 | |
|       item->mValue = Value{};
 | |
|     }
 | |
|     mSize = 0;
 | |
|   }
 | |
| 
 | |
|   struct KeyAndValue {
 | |
|     Key mKey;
 | |
|     Value mValue;
 | |
| 
 | |
|     KeyAndValue() = default;
 | |
|     KeyAndValue(KeyAndValue&&) = default;
 | |
|     KeyAndValue& operator=(KeyAndValue&&) = default;
 | |
|   };
 | |
| 
 | |
|   // Special size value that indicates the cache is in shutdown mode.
 | |
|   constexpr static unsigned ShutdownSize = unsigned(-1);
 | |
| 
 | |
|   mozilla::OffTheBooksMutex mMutex{"LRU cache"};
 | |
|   unsigned mSize = 0;
 | |
|   KeyAndValue mLRUArray[LRUCapacity];
 | |
| #ifdef SMALLARRAYLRUCACHE_STATS
 | |
|   // Hit count for each position in the case. +1 for counting not-found cases.
 | |
|   unsigned mCacheFoundAt[LRUCapacity + 1] = {0u};
 | |
| #endif  // SMALLARRAYLRUCACHE_STATS
 | |
| };
 | |
| 
 | |
| }  // namespace mozilla
 | |
| 
 | |
| #endif  // SmallArrayLRUCache_h
 |