/* Copyright (c) 2017-2026 Hans-Kristian Arntzen * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "object_pool.hpp" #include "intrusive_list.hpp" #include "intrusive_hash_map.hpp" namespace Util { template class LRUCache { public: void set_total_cost(uint64_t cost) { total_cost_limit = cost; } uint64_t get_current_cost() const { return total_cost; } T *find_and_mark_as_recent(uint64_t cookie) { auto *entry = hashmap.find(get_hash(cookie)); if (entry) { lru.move_to_front(lru, entry->get()); return &entry->get()->t; } else return nullptr; } T *allocate(uint64_t cookie, uint64_t cost) { Hash hash = get_hash(cookie); auto *hash_entry = hashmap.find(hash); if (hash_entry) { total_cost += cost - hash_entry->get()->cost; hash_entry->get()->cost = cost; lru.move_to_front(lru, hash_entry->get()); return &hash_entry->get()->t; } total_cost += cost; auto *entry = pool.allocate(); entry->cost = cost; entry->hash = hash; lru.insert_front(entry); hashmap.emplace_replace(get_hash(cookie), lru.begin()); return &entry->t; } uint64_t prune() { uint64_t total_pruned = 0; while (total_cost > total_cost_limit) { auto itr = lru.rbegin(); total_cost -= itr->cost; total_pruned += itr->cost; lru.erase(itr); hashmap.erase(itr->hash); pool.free(itr.get()); } return total_pruned; } bool evict(uint64_t cookie) { auto *entry = hashmap.find(get_hash(cookie)); if (entry) { lru.move_to_back(lru, entry->get()); return true; } else return false; } bool erase(uint64_t cookie) { auto *entry = hashmap.find(get_hash(cookie)); if (entry) { hashmap.erase(entry); lru.erase(entry->get()); pool.free(entry->get().get()); return true; } else return false; } ~LRUCache() { while (!lru.empty()) { auto itr = lru.begin(); lru.erase(itr); pool.free(itr.get()); } } struct CacheEntry : IntrusiveListEnabled { uint64_t cost; Hash hash; T t; }; typename IntrusiveList::Iterator begin() { return lru.begin(); } typename IntrusiveList::Iterator end() { return lru.end(); } private: uint64_t total_cost = 0; uint64_t total_cost_limit = 0; ObjectPool pool; IntrusiveList lru; IntrusiveHashMap::Iterator>> hashmap; static Hash get_hash(uint64_t cookie) { Hasher h; h.u64(cookie); return h.get(); } }; }