example/ex_lru_cache.cc (203 lines of code) (raw):

// SPDX-License-Identifier: Apache-2.0 // Copyright 2021 LinkedIn /** @file Example of building a thread safe LRU cache using intrusive containers. */ #include <unistd.h> #include <chrono> #include <utility> #include <thread> #include <shared_mutex> #include <condition_variable> #include <iostream> #include "swoc/TextView.h" #include "swoc/swoc_ip.h" #include "swoc/bwf_ip.h" #include "swoc/bwf_ex.h" #include "swoc/bwf_std.h" #include "swoc/swoc_file.h" #include "swoc/Errata.h" #include "swoc/IntrusiveDList.h" #include "swoc/IntrusiveHashMap.h" #include "swoc/MemArena.h" using namespace std::literals; using namespace swoc::literals; using swoc::TextView; using swoc::MemSpan; using swoc::Errata; using swoc::IPAddr; using time_point = std::chrono::time_point<std::chrono::system_clock>; template <typename K, typename V> class LRU { using self_type = LRU; public: LRU() = default; self_type &insert(K const &key, V &&value); self_type &erase(K const &key); V retrieve(K const &key) const; size_t count() const { return _table.count(); } protected: struct Item { using self_type = Item; struct Links { self_type *_next = nullptr; self_type *_prev = nullptr; }; K _key; V _value; Links _list; Links _map; Item(self_type &&that) : _key(that._key), _value(std::move(that._value)) {} Item(K const &key, V &&value) : _key(key), _value(std::move(value)) {} self_type & operator=(self_type &&that) { _key = that._key; _value = std::move(that._value); return *this; } }; struct Linkage { static Item *& next_ptr(Item *item) { return item->_list._next; } static Item *& prev_ptr(Item *item) { return item->_list._prev; } }; using List = swoc::IntrusiveDList<Linkage>; struct Hashing { static Item *& next_ptr(Item *item) { return item->_map._next; } static Item *& prev_ptr(Item *item) { return item->_map._prev; } static inline const std::hash<K> Hasher; static K const & key_of(Item *item) { return item->_key; } static decltype(Hasher(std::declval<K>())) hash_of(K const &key) { return Hasher(key); } static bool equal(K const &lhs, K const &rhs) { return lhs == rhs; } }; using Table = swoc::IntrusiveHashMap<Hashing>; std::shared_mutex _mutex; ///< Read/write lock. size_t _max = 1024; ///< Maximum number of elements. List _list; ///< LRU list. Table _table; ///< Keyed set of values. List _free; ///< Free list. swoc::MemArena _arena; }; template <typename K, typename V> auto LRU<K, V>::insert(K const &key, V &&value) -> self_type & { Item *target = nullptr; { std::unique_lock lock(_mutex); auto spot = _table.find(key); if (spot != _table.end()) { spot->_value = std::move(value); } else { Item *item = _free.take_head(); if (item != nullptr) { new (item) Item(key, std::move(value)); } else { item = _arena.make<Item>(key, std::move(value)); } _table.insert(item); _list.append(item); if (_list.count() > _max) { target = _list.take_head(); _table.erase(target); } } } if (target) { target->_key.~K(); target->_value.~V(); std::unique_lock lock(_mutex); _free.append(target); } return *this; } template <typename K, typename V> auto LRU<K, V>::erase(K const &key) -> self_type & { std::unique_lock lock(_mutex); auto spot = _table.find(key); if (spot != _table.end()) { Item *item = spot; _table.erase(item); _list.erase(item); _free.append(item); } return *this; } template <typename K, typename V> auto LRU<K, V>::retrieve(K const &key) const -> V { std::shared_lock lock(_mutex); auto spot = _table.find(key); return spot == _table.end() ? V{} : *spot; } // -------------------------------------------------- int main(int, char *[]) { static constexpr size_t N_THREAD = 16; ///< Number of threads. static constexpr size_t N_ITER = 20000; std::array<std::thread, N_THREAD> threads; std::mutex gate_m; std::condition_variable gate_cv; std::atomic<int> count = -1; struct Data { time_point _expire; int _code = 0; }; LRU<IPAddr, Data> lru; lru.insert(swoc::IP4Addr{"172.17.56.93"}, Data{time_point(), 2}); auto worker = [&]() -> void { unsigned dummy; { std::unique_lock _(gate_m); gate_cv.wait(_, [&]() { return count >= 0; }); } swoc::IP4Addr addr((reinterpret_cast<uintptr_t>(&dummy) >> 16) & 0xFFFFFFFF); auto tp = time_point(); for (unsigned i = 0; i < N_ITER; ++i) { lru.insert(addr, Data{tp, 1}); addr = htonl(addr.host_order() + 1); } { std::unique_lock _(gate_m); ++count; } gate_cv.notify_all(); }; for (unsigned i = 0; i < N_THREAD; ++i) { threads[i] = std::thread(worker); } auto t0 = std::chrono::system_clock::now(); { std::unique_lock _(gate_m); count = 0; gate_cv.notify_all(); } { std::unique_lock _(gate_m); gate_cv.wait(_, [&]() { return count == N_THREAD; }); } auto tf = std::chrono::system_clock::now(); auto delta = tf - t0; std::cout << "Done in " << delta.count() << " with " << lru.count() << std::endl; std::cout << "ns per operation " << (delta.count() / (N_THREAD * N_ITER)) << std::endl; for (unsigned i = 0; i < N_THREAD; ++i) { threads[i].join(); } return 0; }