Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Core::FIFOCache #79

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions src/Core/FIFOCache.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/** Copyright 2024 RWTH Aachen University. All rights reserved.
*
* Licensed under the RWTH ASR License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.hltpr.rwth-aachen.de/rwth-asr/rwth-asr-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef FIFO_CACHE_HH
#define FIFO_CACHE_HH

#include <cstddef>
#include <unordered_map>
#include <vector>

namespace Core {

/*
* Cache based on an unordered_map with a maximum size such that when the cache is full and
* a new item is trying to be added, the oldest item in the cache gets removed.
* Note: oldest is determined by order of insertion, not order of last access.
*
* Example:
*
* FIFOCache<int, std::string> cache(2); // Cache has room for two items
* cache.put(1, "one"); // Internal data: {1: "one"}
* cache.put(2, "two"); // Internal data: {1: "one", 2: "two"}
* cache.put(3, "three"); // Oldest element is deleted since max size was reached. Internal data: {2: "two", 3: "three"}
*/
template<typename Key, typename Value, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>>
class FIFOCache {
public:
FIFOCache(size_t maxSize);

// Insert or update a key-value pair (oldest inserted elements get removed first)
void put(const Key& key, const Value& value);

Value& get(const Key& key);
bool contains(const Key& key) const;
void clear();
size_t size() const;
size_t maxSize() const;

private:
using Map = typename std::unordered_map<Key, Value, Hash, KeyEqual>;
using MapIterator = typename std::unordered_map<Key, Value, Hash, KeyEqual>::iterator;

Map cacheMap_;
size_t maxSize_;

// Ring-vector with the keys to all the elements of the cacheMap in order of insertion.
std::vector<Key> cacheElementKeys_;
size_t oldestElementPos_; // Position of the oldest element inside `cacheElementIters_`
};

/*
* Implementations
*/

template<typename Key, typename Value, typename Hash, typename KeyEqual>
FIFOCache<Key, Value, Hash, KeyEqual>::FIFOCache(size_t maxSize)
: cacheMap_(), maxSize_(maxSize), cacheElementKeys_(), oldestElementPos_(0ul) {
cacheMap_.reserve(maxSize_);
cacheElementKeys_.reserve(maxSize_);
}

// Insert or update a key-value pair (oldest inserted elements get removed first)
template<typename Key, typename Value, typename Hash, typename KeyEqual>
void FIFOCache<Key, Value, Hash, KeyEqual>::put(const Key& key, const Value& value) {
auto it = cacheMap_.find(key);
if (it != cacheMap_.end()) {
// Key exists; update the value but don't change the order in the list
it->second = value;
}
else { // Key doesn't exist yet

SimBe195 marked this conversation as resolved.
Show resolved Hide resolved
// If the cache is not full yet, just insert the new item
if (cacheElementKeys_.size() < maxSize_) {
cacheMap_.emplace(key, value);
cacheElementKeys_.push_back(key); // Store key of the newly inserted element
}
else { // Cache is full -> replace oldest item with the new item
cacheMap_.erase(cacheElementKeys_[oldestElementPos_]);
cacheMap_.emplace(key, value);

// Replace old key in vector with new key
cacheElementKeys_[oldestElementPos_] = key;

// Next oldest element becomes new oldest
oldestElementPos_ = (oldestElementPos_ + 1ul) % maxSize_; // Wrap position around at the end of the vector
}
}
}

template<typename Key, typename Value, typename Hash, typename KeyEqual>
Value& FIFOCache<Key, Value, Hash, KeyEqual>::get(const Key& key) {
return cacheMap_.at(key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throws an exception if the key is not present. As nobody ever catches these it will probably terminate the application. I think it would be better to return std::optional<Value&> and for an interface the default-constructs the value add an operator[] function.

For the version returning std::optional<Value&> we could also have a const version of the function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C++ unfortunately doesn't support optional references so std::optional<Value&> doesn't work . I have implemented this now by using std::optional<std::reference_wrapper<Value>>. It could also be done by using Value* as return value instead and returning nullptr when the key is not found.

}

template<typename Key, typename Value, typename Hash, typename KeyEqual>
bool FIFOCache<Key, Value, Hash, KeyEqual>::contains(const Key& key) const {
return cacheMap_.find(key) != cacheMap_.end();
}

template<typename Key, typename Value, typename Hash, typename KeyEqual>
void FIFOCache<Key, Value, Hash, KeyEqual>::clear() {
cacheMap_.clear();
cacheElementKeys_.clear();
oldestElementPos_ = 0ul;
}

template<typename Key, typename Value, typename Hash, typename KeyEqual>
size_t FIFOCache<Key, Value, Hash, KeyEqual>::size() const {
return cacheMap_.size();
}

template<typename Key, typename Value, typename Hash, typename KeyEqual>
size_t FIFOCache<Key, Value, Hash, KeyEqual>::maxSize() const {
return maxSize_;
}

} // namespace Core

#endif // FIFO_CACHE_HH