Make cache MAX_SIZE configurable

This commit is contained in:
Sebastian Messmer 2015-10-07 17:24:13 +02:00
parent 810c2c5b48
commit 260bc1056a
3 changed files with 27 additions and 30 deletions

View File

@ -26,7 +26,7 @@ public:
private:
cpputils::unique_ref<BlockStore> _baseBlockStore;
Cache<Key, cpputils::unique_ref<Block>> _cache;
Cache<Key, cpputils::unique_ref<Block>, 1000> _cache;
uint32_t _numNewBlocks;
DISALLOW_COPY_AND_ASSIGN(CachingBlockStore);

View File

@ -14,10 +14,9 @@
namespace blockstore {
namespace caching {
template<class Key, class Value>
template<class Key, class Value, uint32_t MAX_ENTRIES>
class Cache {
public:
static constexpr uint32_t MAX_ENTRIES = 1000;
//TODO Experiment with good values
static constexpr double PURGE_LIFETIME_SEC = 0.5; //When an entry has this age, it will be purged from the cache
static constexpr double PURGE_INTERVAL = 0.5; // With this interval, we check for entries to purge
@ -44,24 +43,23 @@ private:
std::unique_ptr<PeriodicTask> _timeoutFlusher;
};
template<class Key, class Value> constexpr uint32_t Cache<Key, Value>::MAX_ENTRIES;
template<class Key, class Value> constexpr double Cache<Key, Value>::PURGE_LIFETIME_SEC;
template<class Key, class Value> constexpr double Cache<Key, Value>::PURGE_INTERVAL;
template<class Key, class Value> constexpr double Cache<Key, Value>::MAX_LIFETIME_SEC;
template<class Key, class Value, uint32_t MAX_ENTRIES> constexpr double Cache<Key, Value, MAX_ENTRIES>::PURGE_LIFETIME_SEC;
template<class Key, class Value, uint32_t MAX_ENTRIES> constexpr double Cache<Key, Value, MAX_ENTRIES>::PURGE_INTERVAL;
template<class Key, class Value, uint32_t MAX_ENTRIES> constexpr double Cache<Key, Value, MAX_ENTRIES>::MAX_LIFETIME_SEC;
template<class Key, class Value>
Cache<Key, Value>::Cache(): _cachedBlocks(), _timeoutFlusher(nullptr) {
template<class Key, class Value, uint32_t MAX_ENTRIES>
Cache<Key, Value, MAX_ENTRIES>::Cache(): _cachedBlocks(), _timeoutFlusher(nullptr) {
//Don't initialize timeoutFlusher in the initializer list,
//because it then might already call Cache::popOldEntries() before Cache is done constructing.
_timeoutFlusher = std::make_unique<PeriodicTask>(std::bind(&Cache::_deleteOldEntriesParallel, this), PURGE_INTERVAL);
}
template<class Key, class Value>
Cache<Key, Value>::~Cache() {
template<class Key, class Value, uint32_t MAX_ENTRIES>
Cache<Key, Value, MAX_ENTRIES>::~Cache() {
}
template<class Key, class Value>
boost::optional<Value> Cache<Key, Value>::pop(const Key &key) {
template<class Key, class Value, uint32_t MAX_ENTRIES>
boost::optional<Value> Cache<Key, Value, MAX_ENTRIES>::pop(const Key &key) {
std::unique_lock<std::mutex> lock(_mutex);
cpputils::MutexPoolLock<Key> lockEntryFromBeingPopped(&_currentlyFlushingEntries, key, &lock);
@ -72,16 +70,17 @@ boost::optional<Value> Cache<Key, Value>::pop(const Key &key) {
return found->releaseValue();
}
template<class Key, class Value>
void Cache<Key, Value>::push(const Key &key, Value value) {
template<class Key, class Value, uint32_t MAX_ENTRIES>
void Cache<Key, Value, MAX_ENTRIES>::push(const Key &key, Value value) {
std::unique_lock<std::mutex> lock(_mutex);
//std::cout << "Pushing " << key.ToString() << "\n";
ASSERT(_cachedBlocks.size() <= MAX_ENTRIES, "Cache too full");
_makeSpaceForEntry(&lock);
_cachedBlocks.push(key, CacheEntry<Key, Value>(std::move(value)));
}
template<class Key, class Value>
void Cache<Key, Value>::_makeSpaceForEntry(std::unique_lock<std::mutex> *lock) {
template<class Key, class Value, uint32_t MAX_ENTRIES>
void Cache<Key, Value, MAX_ENTRIES>::_makeSpaceForEntry(std::unique_lock<std::mutex> *lock) {
// _deleteEntry releases the lock while the Value destructor is running.
// So we can destruct multiple entries in parallel and also call pop() or push() while doing so.
// However, if another thread calls push() before we get the lock back, the cache is full again.
@ -92,8 +91,8 @@ void Cache<Key, Value>::_makeSpaceForEntry(std::unique_lock<std::mutex> *lock) {
ASSERT(_cachedBlocks.size() < MAX_ENTRIES, "Removing entry from cache didn't work");
};
template<class Key, class Value>
void Cache<Key, Value>::_deleteEntry(std::unique_lock<std::mutex> *lock) {
template<class Key, class Value, uint32_t MAX_ENTRIES>
void Cache<Key, Value, MAX_ENTRIES>::_deleteEntry(std::unique_lock<std::mutex> *lock) {
auto key = _cachedBlocks.peekKey();
ASSERT(key != boost::none, "There was no entry to delete");
cpputils::MutexPoolLock<Key> lockEntryFromBeingPopped(&_currentlyFlushingEntries, *key);
@ -105,8 +104,8 @@ void Cache<Key, Value>::_deleteEntry(std::unique_lock<std::mutex> *lock) {
lock->lock();
};
template<class Key, class Value>
void Cache<Key, Value>::_deleteOldEntriesParallel() {
template<class Key, class Value, uint32_t MAX_ENTRIES>
void Cache<Key, Value, MAX_ENTRIES>::_deleteOldEntriesParallel() {
unsigned int numThreads = std::max(1u, std::thread::hardware_concurrency());
std::vector<std::future<void>> waitHandles;
for (unsigned int i = 0; i < numThreads; ++i) {
@ -119,13 +118,13 @@ void Cache<Key, Value>::_deleteOldEntriesParallel() {
}
};
template<class Key, class Value>
void Cache<Key, Value>::_deleteOldEntries() {
template<class Key, class Value, uint32_t MAX_ENTRIES>
void Cache<Key, Value, MAX_ENTRIES>::_deleteOldEntries() {
while (_deleteOldEntry()) {}
}
template<class Key, class Value>
bool Cache<Key, Value>::_deleteOldEntry() {
template<class Key, class Value, uint32_t MAX_ENTRIES>
bool Cache<Key, Value, MAX_ENTRIES>::_deleteOldEntry() {
// This function can be called in parallel by multiple threads and will then cause the Value destructors
// to be called in parallel. The call to _deleteEntry() releases the lock while the Value destructor is running.
std::unique_lock<std::mutex> lock(_mutex);
@ -137,8 +136,8 @@ bool Cache<Key, Value>::_deleteOldEntry() {
}
};
template<class Key, class Value>
uint32_t Cache<Key, Value>::size() const {
template<class Key, class Value, uint32_t MAX_ENTRIES>
uint32_t Cache<Key, Value, MAX_ENTRIES>::size() const {
std::unique_lock<std::mutex> lock(_mutex);
return _cachedBlocks.size();
};

View File

@ -25,9 +25,7 @@ public:
void push(const Key &key, Value value) {
auto newEntry = _entries.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(_sentinel.prev, &_sentinel));
if(newEntry.second != true) {
throw std::logic_error("There is already an element with this key");
}
ASSERT(newEntry.second == true, "There is already an element with this key");
newEntry.first->second.init(&newEntry.first->first, std::move(value));
//The following is ok, because std::unordered_map never invalidates pointers to its entries
_sentinel.prev->next = &newEntry.first->second;