Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
173
libs/base/lru_cache.hpp
Normal file
173
libs/base/lru_cache.hpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
/// \brief Implementation of cache with least recently used replacement policy.
|
||||
template <typename Key, typename Value>
|
||||
class LruCache
|
||||
{
|
||||
template <typename K, typename V>
|
||||
friend class LruCacheTest;
|
||||
template <typename K, typename V>
|
||||
friend class LruCacheKeyAgeTest;
|
||||
|
||||
public:
|
||||
/// \param maxCacheSize Maximum size of the cache in number of items. It should be one or greater.
|
||||
/// \param loader Function which is called if it's necessary to load a new item for the cache.
|
||||
/// For the same |key| should be loaded the same |value|.
|
||||
explicit LruCache(size_t maxCacheSize) : m_maxCacheSize(maxCacheSize) { CHECK_GREATER(maxCacheSize, 0, ()); }
|
||||
|
||||
// Find value by @key. If @key is found, returns reference to its value.
|
||||
Value & Find(Key const & key, bool & found)
|
||||
{
|
||||
auto const it = m_cache.find(key);
|
||||
if (it != m_cache.cend())
|
||||
{
|
||||
m_keyAge.UpdateAge(key);
|
||||
found = true;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
if (m_cache.size() >= m_maxCacheSize)
|
||||
{
|
||||
m_cache.erase(m_keyAge.GetLruKey());
|
||||
m_keyAge.RemoveLru();
|
||||
}
|
||||
|
||||
m_keyAge.InsertKey(key);
|
||||
Value & value = m_cache[key];
|
||||
found = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
m_cache.clear();
|
||||
m_keyAge.Clear();
|
||||
}
|
||||
|
||||
/// \brief Checks for coherence class params.
|
||||
/// \note It's a time consumption method and should be called for tests only.
|
||||
bool IsValidForTesting() const
|
||||
{
|
||||
if (!m_keyAge.IsValidForTesting())
|
||||
return false;
|
||||
|
||||
for (auto const & kv : m_cache)
|
||||
if (!m_keyAge.IsKeyValidForTesting(kv.first))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
/// \brief This class support cross mapping from age to key and for key to age.
|
||||
/// It lets effectively get least recently used key (key with minimum value of age)
|
||||
/// and find key age by its value to update the key age.
|
||||
/// \note Size of |m_ageToKey| and |m_ageToKey| should be the same.
|
||||
/// All keys of |m_ageToKey| should be values of |m_ageToKey| and on the contrary
|
||||
/// all keys of |m_ageToKey| should be values of |m_ageToKey|.
|
||||
/// \note Ages should be unique for all keys.
|
||||
class KeyAge
|
||||
{
|
||||
template <typename K, typename V>
|
||||
friend class LruCacheKeyAgeTest;
|
||||
|
||||
public:
|
||||
void Clear()
|
||||
{
|
||||
m_age = 0;
|
||||
m_ageToKey.clear();
|
||||
m_keyToAge.clear();
|
||||
}
|
||||
|
||||
/// \brief Increments |m_age| and insert key to |m_ageToKey| and |m_keyToAge|.
|
||||
/// \note This method should be used only if there's no |key| in |m_ageToKey| and |m_keyToAge|.
|
||||
void InsertKey(Key const & key)
|
||||
{
|
||||
++m_age;
|
||||
m_ageToKey[m_age] = key;
|
||||
m_keyToAge[key] = m_age;
|
||||
}
|
||||
|
||||
/// \brief Increments |m_age| and updates key age in |m_ageToKey| and |m_keyToAge|.
|
||||
/// \note This method should be used only if there's |key| in |m_ageToKey| and |m_keyToAge|.
|
||||
void UpdateAge(Key const & key)
|
||||
{
|
||||
++m_age;
|
||||
auto keyToAgeIt = m_keyToAge.find(key);
|
||||
CHECK(keyToAgeIt != m_keyToAge.end(), ());
|
||||
// Removing former age.
|
||||
size_t const removed = m_ageToKey.erase(keyToAgeIt->second);
|
||||
CHECK_EQUAL(removed, 1, ());
|
||||
// Putting new age.
|
||||
m_ageToKey[m_age] = key;
|
||||
keyToAgeIt->second = m_age;
|
||||
}
|
||||
|
||||
/// \returns Least recently used key without updating the age.
|
||||
/// \note |m_ageToKey| and |m_keyToAge| shouldn't be empty.
|
||||
Key const & GetLruKey() const
|
||||
{
|
||||
CHECK(!m_ageToKey.empty(), ());
|
||||
// The smaller age the older item.
|
||||
return m_ageToKey.cbegin()->second;
|
||||
}
|
||||
|
||||
void RemoveLru()
|
||||
{
|
||||
Key const & lru = GetLruKey();
|
||||
size_t const removed = m_keyToAge.erase(lru);
|
||||
CHECK_EQUAL(removed, 1, ());
|
||||
m_ageToKey.erase(m_ageToKey.begin());
|
||||
}
|
||||
|
||||
/// \brief Checks for coherence class params.
|
||||
/// \note It's a time consumption method and should be called for tests only.
|
||||
bool IsValidForTesting() const
|
||||
{
|
||||
for (auto const & kv : m_ageToKey)
|
||||
{
|
||||
if (kv.first > m_age)
|
||||
return false;
|
||||
if (m_keyToAge.find(kv.second) == m_keyToAge.cend())
|
||||
return false;
|
||||
}
|
||||
for (auto const & kv : m_keyToAge)
|
||||
{
|
||||
if (kv.second > m_age)
|
||||
return false;
|
||||
if (m_ageToKey.find(kv.second) == m_ageToKey.cend())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \returns true if |key| and its age are contained in |m_keyToAge| and |m_keyToAge|.
|
||||
/// \note It's a time consumption method and should be called for tests only.
|
||||
bool IsKeyValidForTesting(Key const & key) const
|
||||
{
|
||||
auto const keyToAgeId = m_keyToAge.find(key);
|
||||
if (keyToAgeId == m_keyToAge.cend())
|
||||
return false;
|
||||
auto const ageToKeyIt = m_ageToKey.find(keyToAgeId->second);
|
||||
if (ageToKeyIt == m_ageToKey.cend())
|
||||
return false;
|
||||
return key == ageToKeyIt->second;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_age = 0;
|
||||
std::map<size_t, Key> m_ageToKey;
|
||||
std::unordered_map<Key, size_t> m_keyToAge;
|
||||
};
|
||||
|
||||
size_t const m_maxCacheSize;
|
||||
std::unordered_map<Key, Value> m_cache;
|
||||
KeyAge m_keyAge;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue