From 52a15fc2bc7f9d5dc725fa53a1256c3fa29599e7 Mon Sep 17 00:00:00 2001 From: ZHANG MING <191266563+FILWYZ@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:05:04 +0800 Subject: [PATCH] Harden concurrent cache project --- .cache_benchmark_build/cache_benchmark | Bin 119184 -> 0 bytes .cache_benchmark_build/cache_benchmark.cpp | 343 --------- .cache_benchmark_build/include/ARC.h | 738 ------------------- .cache_benchmark_build/include/CachePolicy.h | 221 ------ .cache_benchmark_build/include/LFU.h | 590 --------------- .cache_benchmark_build/include/LRU.h | 691 ----------------- .github/workflows/ci.yml | 37 + .gitignore | 2 + CMakeLists.txt | 47 ++ LICENSE | 21 + LRU.h | 144 +++- README.md | 162 +++- tests/cache_tests.cpp | 136 ++++ 13 files changed, 476 insertions(+), 2656 deletions(-) delete mode 100755 .cache_benchmark_build/cache_benchmark delete mode 100644 .cache_benchmark_build/cache_benchmark.cpp delete mode 100644 .cache_benchmark_build/include/ARC.h delete mode 100644 .cache_benchmark_build/include/CachePolicy.h delete mode 100644 .cache_benchmark_build/include/LFU.h delete mode 100644 .cache_benchmark_build/include/LRU.h create mode 100644 .github/workflows/ci.yml create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 tests/cache_tests.cpp diff --git a/.cache_benchmark_build/cache_benchmark b/.cache_benchmark_build/cache_benchmark deleted file mode 100755 index eb757beb0a7245418bcd91ef0a1d83d98bef9ec9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119184 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 Ra>u79?!NHi_ZzX_{y#N+entQQ diff --git a/.cache_benchmark_build/cache_benchmark.cpp b/.cache_benchmark_build/cache_benchmark.cpp deleted file mode 100644 index 385276c..0000000 --- a/.cache_benchmark_build/cache_benchmark.cpp +++ /dev/null @@ -1,343 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CachePolicy.h" -#include "LRU.h" -#include "LFU.h" -#include "ARC.h" - -using Key = int; -using Value = int; - -struct Operation { - // 0 = get, 1 = put - std::uint8_t type{}; - Key key{}; - Value value{}; -}; - -struct BenchResult { - std::string name; - double milliseconds{}; - double mops{}; - std::uint64_t gets{}; - std::uint64_t puts{}; - std::uint64_t hits{}; - std::uint64_t misses{}; - std::uint64_t evictions{}; - bool evictionsKnown{}; - std::size_t finalSize{}; -}; - -struct LocalCounters { - std::uint64_t gets{}; - std::uint64_t puts{}; - std::uint64_t hits{}; - std::uint64_t misses{}; -}; - -class LRUAdapter { -public: - LRUAdapter(std::size_t capacity, std::size_t shards) - : cache_(capacity, static_cast(std::max(1, shards))) {} - - bool get(const Key& key, Value& value) { return cache_.get(key, value); } - void put(const Key& key, const Value& value) { cache_.put(key, value); } - std::size_t size() const { return cache_.size(); } - std::uint64_t evictions() const { return 0; } - bool evictionsKnown() const { return false; } - -private: - KamaCache::KHighConcurrencyCache cache_; -}; - -class LRUKAdapter { -public: - LRUKAdapter(std::size_t capacity, std::size_t shards) - : cache_(capacity, capacity, 2, static_cast(std::max(1, shards))) {} - - bool get(const Key& key, Value& value) { return cache_.get(key, value); } - void put(const Key& key, const Value& value) { cache_.put(key, value); } - std::size_t size() const { return cache_.mainSize() + cache_.historySize(); } - std::uint64_t evictions() const { return 0; } - bool evictionsKnown() const { return false; } - -private: - KamaCache::KLruKCache cache_; -}; - -class LFUAdapter { -public: - LFUAdapter(std::size_t capacity, std::size_t shards) - : cache_(capacity, std::max(1, shards)) {} - - bool get(const Key& key, Value& value) { return cache_.get(key, value); } - void put(const Key& key, const Value& value) { cache_.put(key, value); } - std::size_t size() const { return cache_.size(); } - std::uint64_t evictions() const { return cache_.stats().evictions; } - bool evictionsKnown() const { return true; } - -private: - KamaCache::ShardedLFUCache cache_; -}; - -class ARCAdapter { -public: - ARCAdapter(std::size_t capacity, std::size_t shards) - : cache_(capacity, std::max(1, shards)) {} - - bool get(const Key& key, Value& value) { return cache_.get(key, value); } - void put(const Key& key, const Value& value) { cache_.put(key, value); } - std::size_t size() const { return cache_.size(); } - std::uint64_t evictions() const { return cache_.stats().evictions; } - bool evictionsKnown() const { return true; } - -private: - KamaCache::ShardedARCCache cache_; -}; - -std::vector makeMixedWorkload( - std::size_t operations, - std::size_t keyspace, - std::size_t hotKeys, - double getRatio, - double hotRatio, - std::uint64_t seed) -{ - std::vector workload; - workload.reserve(operations); - - std::mt19937_64 rng(seed); - std::uniform_real_distribution prob(0.0, 1.0); - std::uniform_int_distribution hotDist(0, static_cast(std::max(1, hotKeys) - 1)); - std::uniform_int_distribution coldDist( - static_cast(std::max(1, hotKeys)), - static_cast(std::max(hotKeys + 1, keyspace) - 1)); - - for (std::size_t i = 0; i < operations; ++i) { - const bool isGet = prob(rng) < getRatio; - const bool isHot = prob(rng) < hotRatio; - const int key = isHot ? hotDist(rng) : coldDist(rng); - workload.push_back(Operation{ - static_cast(isGet ? 0 : 1), - key, - key - }); - } - - return workload; -} - -std::vector makeScanWorkload( - std::size_t operations, - std::size_t keyspace, - double getRatio) -{ - std::vector workload; - workload.reserve(operations); - - for (std::size_t i = 0; i < operations; ++i) { - const bool isGet = static_cast(i % 1000) / 1000.0 < getRatio; - const int key = static_cast(i % keyspace); - workload.push_back(Operation{ - static_cast(isGet ? 0 : 1), - key, - key - }); - } - - return workload; -} - -template -BenchResult runBenchmark( - const std::string& name, - const std::vector& workload, - std::size_t capacity, - std::size_t shards, - std::size_t threadCount) -{ - Adapter cache(capacity, shards); - - // Prefill: keep every policy at a comparable warm starting point. - for (std::size_t i = 0; i < capacity; ++i) { - cache.put(static_cast(i), static_cast(i)); - } - - threadCount = std::max(1, threadCount); - std::vector counters(threadCount); - std::vector workers; - workers.reserve(threadCount); - - const auto start = std::chrono::steady_clock::now(); - - for (std::size_t t = 0; t < threadCount; ++t) { - const std::size_t begin = workload.size() * t / threadCount; - const std::size_t end = workload.size() * (t + 1) / threadCount; - - workers.emplace_back([&, begin, end, t]() { - Value value{}; - LocalCounters local; - - for (std::size_t i = begin; i < end; ++i) { - const Operation& op = workload[i]; - - if (op.type == 0) { - ++local.gets; - if (cache.get(op.key, value)) { - ++local.hits; - } else { - ++local.misses; - // Read-through cache behavior: miss then insert. - cache.put(op.key, op.value); - ++local.puts; - } - } else { - cache.put(op.key, op.value); - ++local.puts; - } - } - - counters[t] = local; - }); - } - - for (auto& worker : workers) { - worker.join(); - } - - const auto finish = std::chrono::steady_clock::now(); - const double ms = std::chrono::duration(finish - start).count(); - - BenchResult result; - result.name = name; - result.milliseconds = ms; - result.mops = workload.empty() || ms == 0.0 - ? 0.0 - : static_cast(workload.size()) / ms / 1000.0; - - for (const auto& c : counters) { - result.gets += c.gets; - result.puts += c.puts; - result.hits += c.hits; - result.misses += c.misses; - } - - result.evictions = cache.evictions(); - result.evictionsKnown = cache.evictionsKnown(); - result.finalSize = cache.size(); - return result; -} - -void printResult(const BenchResult& r) -{ - const double hitRate = r.gets == 0 ? 0.0 : static_cast(r.hits) * 100.0 / r.gets; - const double missRate = r.gets == 0 ? 0.0 : static_cast(r.misses) * 100.0 / r.gets; - - std::cout - << std::left << std::setw(14) << r.name - << std::right << std::setw(12) << std::fixed << std::setprecision(2) << r.milliseconds - << std::setw(12) << std::fixed << std::setprecision(3) << r.mops - << std::setw(12) << std::fixed << std::setprecision(2) << hitRate - << std::setw(12) << std::fixed << std::setprecision(2) << missRate - << std::setw(14) << r.puts - << std::setw(14) << r.finalSize; - - if (r.evictionsKnown) { - std::cout << std::setw(14) << r.evictions; - } else { - std::cout << std::setw(14) << "N/A"; - } - - std::cout << '\n'; -} - -void printHeader() -{ - std::cout - << std::left << std::setw(14) << "cache" - << std::right << std::setw(12) << "ms" - << std::setw(12) << "Mops/s" - << std::setw(12) << "hit%" - << std::setw(12) << "miss%" - << std::setw(14) << "puts" - << std::setw(14) << "final_size" - << std::setw(14) << "evictions" - << '\n'; - - std::cout << std::string(104, '-') << '\n'; -} - -void runScenario( - const std::string& scenarioName, - const std::vector& workload, - std::size_t capacity, - std::size_t shards, - std::size_t threads) -{ - std::cout << "\nScenario: " << scenarioName << '\n'; - printHeader(); - printResult(runBenchmark("LRU", workload, capacity, shards, threads)); - printResult(runBenchmark("LRU-K(k=2)", workload, capacity, shards, threads)); - printResult(runBenchmark("LFU", workload, capacity, shards, threads)); - printResult(runBenchmark("ARC", workload, capacity, shards, threads)); -} - -int main(int argc, char** argv) -{ - const std::size_t operations = argc > 1 ? static_cast(std::strtoull(argv[1], nullptr, 10)) : 1000000; - const std::size_t capacity = argc > 2 ? static_cast(std::strtoull(argv[2], nullptr, 10)) : 16384; - const std::size_t threads = argc > 3 ? static_cast(std::strtoull(argv[3], nullptr, 10)) : 1; - const std::size_t shards = argc > 4 ? static_cast(std::strtoull(argv[4], nullptr, 10)) : threads; - - const std::size_t keyspace = std::max(capacity * 16, capacity + 1); - const std::size_t hotKeys = std::max(64, capacity / 8); - - std::cout << "cache benchmark\n" - << "operations_per_scenario=" << operations - << ", capacity=" << capacity - << ", keyspace=" << keyspace - << ", hot_keys=" << hotKeys - << ", threads=" << threads - << ", shards=" << shards - << "\n"; - - runScenario( - "hot_read_95_get_90_hot", - makeMixedWorkload(operations, keyspace, hotKeys, 0.95, 0.90, 42), - capacity, - shards, - threads); - - runScenario( - "mixed_80_get_80_hot", - makeMixedWorkload(operations, keyspace, hotKeys, 0.80, 0.80, 43), - capacity, - shards, - threads); - - runScenario( - "scan_95_get", - makeScanWorkload(operations, keyspace, 0.95), - capacity, - shards, - threads); - - runScenario( - "write_heavy_50_get_70_hot", - makeMixedWorkload(operations, keyspace, hotKeys, 0.50, 0.70, 44), - capacity, - shards, - threads); - - return 0; -} diff --git a/.cache_benchmark_build/include/ARC.h b/.cache_benchmark_build/include/ARC.h deleted file mode 100644 index 2588c27..0000000 --- a/.cache_benchmark_build/include/ARC.h +++ /dev/null @@ -1,738 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CachePolicy.h" - -namespace KamaCache { - -struct ARCState { - std::size_t recent{0}; - std::size_t frequent{0}; - std::size_t recentGhost{0}; - std::size_t frequentGhost{0}; - std::size_t recentTarget{0}; - - [[nodiscard]] std::size_t resident() const noexcept - { - return recent + frequent; - } - - [[nodiscard]] std::size_t ghost() const noexcept - { - return recentGhost + frequentGhost; - } - - [[nodiscard]] std::size_t tracked() const noexcept - { - return resident() + ghost(); - } -}; - -// ============================================================================ -// 1. 单分片 ARC 缓存 -// -// ARC = Adaptive Replacement Cache -// -// 四个核心队列: -// 1. recent_ : T1,最近只访问过一次的真实缓存数据 -// 2. frequent_ : T2,访问过至少两次的真实缓存数据 -// 3. recentGhost_ : B1,recent_ 被淘汰后的 key 历史,不保存 value -// 4. frequentGhost_ : B2,frequent_ 被淘汰后的 key 历史,不保存 value -// -// recentTarget_ 对应 ARC 论文中的 p: -// - B1 命中:说明 recent 区太小,增大 recentTarget_ -// - B2 命中:说明 frequent 区太小,减小 recentTarget_ -// -// 工程优化: -// 1. resident 节点使用 list 保存 key/value/listId -// 2. residentIndex_ 只保存 Key -> ResidentIterator -// 3. ghost 节点使用 list 保存 key/listId -// 4. ghostIndex_ 只保存 Key -> GhostIterator -// 5. 节点迁移使用 list::splice,避免重复构造 key/value -// 6. 分片内一把 mutex,保证单 shard 状态一致 -// ============================================================================ -template < - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to> -class alignas(64) ARCCache final : public CachePolicy { -public: - explicit ARCCache(std::size_t capacity) - : ARCCache(capacity, Hash{}, KeyEqual{}) - { - } - - ARCCache(std::size_t capacity, const Hash& hash, const KeyEqual& equal) - : capacity_(capacity) - , residentIndex_(0, hash, equal) - , ghostIndex_(0, hash, equal) - { - residentIndex_.reserve(capacity_); - ghostIndex_.reserve(capacity_); - } - - ~ARCCache() override = default; - - ARCCache(const ARCCache&) = delete; - ARCCache& operator=(const ARCCache&) = delete; - - ARCCache(ARCCache&&) = delete; - ARCCache& operator=(ARCCache&&) = delete; - - void put(const Key& key, const Value& value) override - { - static_cast(putAndReport(key, value)); - } - - template - CacheWriteResult putAndReport(K&& key, V&& value) - { - if (capacity_ == 0) { - return CacheWriteResult::ignored; - } - - std::lock_guard lock(mutex_); - - auto resident = residentIndex_.find(key); - if (resident != residentIndex_.end()) { - resident->second->value = std::forward(value); - promoteToFrequent(resident->second); - return CacheWriteResult::updated; - } - - auto ghost = ghostIndex_.find(key); - if (ghost != ghostIndex_.end()) { - const Key stableKey = ghost->second->key; - const bool frequentGhostHit = - ghost->second->list == ListId::frequentGhost; - - adaptTarget(frequentGhostHit); - const bool evicted = replace(frequentGhostHit); - - // replace() 可能向 ghostIndex_ 插入新元素并触发 rehash, - // 因此这里必须重新查找,不能继续使用 replace() 之前的 ghost 迭代器。 - insertResident(stableKey, std::forward(value), ListId::frequent); - - auto ghostAfterReplace = ghostIndex_.find(stableKey); - if (ghostAfterReplace != ghostIndex_.end()) { - eraseGhost(ghostAfterReplace); - } - - trimGhostHistory(); - - return evicted - ? CacheWriteResult::insertedWithEviction - : CacheWriteResult::inserted; - } - - const bool evicted = insertCold( - std::forward(key), - std::forward(value)); - - trimGhostHistory(); - - return evicted - ? CacheWriteResult::insertedWithEviction - : CacheWriteResult::inserted; - } - - bool get(const Key& key, Value& value) override - { - std::lock_guard lock(mutex_); - - auto found = residentIndex_.find(key); - if (found == residentIndex_.end()) { - misses_.fetch_add(1, std::memory_order_relaxed); - return false; - } - - value = found->second->value; - promoteToFrequent(found->second); - - hits_.fetch_add(1, std::memory_order_relaxed); - return true; - } - - using CachePolicy::get; - - [[nodiscard]] bool peek(const Key& key, Value& value) const override - { - std::lock_guard lock(mutex_); - - const auto found = residentIndex_.find(key); - if (found == residentIndex_.end()) { - return false; - } - - value = found->second->value; - return true; - } - - bool erase(const Key& key) override - { - std::lock_guard lock(mutex_); - - auto resident = residentIndex_.find(key); - if (resident != residentIndex_.end()) { - eraseResident(resident); - return true; - } - - auto ghost = ghostIndex_.find(key); - if (ghost == ghostIndex_.end()) { - return false; - } - - eraseGhost(ghost); - return true; - } - - void clear() override - { - std::lock_guard lock(mutex_); - - residentIndex_.clear(); - ghostIndex_.clear(); - - recent_.clear(); - frequent_.clear(); - recentGhost_.clear(); - frequentGhost_.clear(); - - recentTarget_ = 0; - } - - void purge() - { - clear(); - } - - [[nodiscard]] bool contains(const Key& key) const override - { - std::lock_guard lock(mutex_); - return residentIndex_.find(key) != residentIndex_.end(); - } - - [[nodiscard]] bool isGhost(const Key& key) const - { - std::lock_guard lock(mutex_); - return ghostIndex_.find(key) != ghostIndex_.end(); - } - - [[nodiscard]] std::size_t size() const override - { - std::lock_guard lock(mutex_); - return residentIndex_.size(); - } - - [[nodiscard]] std::size_t capacity() const noexcept override - { - return capacity_; - } - - [[nodiscard]] ARCState state() const - { - std::lock_guard lock(mutex_); - - return ARCState{ - recent_.size(), - frequent_.size(), - recentGhost_.size(), - frequentGhost_.size(), - recentTarget_, - }; - } - - [[nodiscard]] CacheStats stats() const noexcept override - { - return CacheStats{ - hits_.load(std::memory_order_relaxed), - misses_.load(std::memory_order_relaxed), - evictions_.load(std::memory_order_relaxed), - }; - } - - void resetStats() noexcept override - { - hits_.store(0, std::memory_order_relaxed); - misses_.store(0, std::memory_order_relaxed); - evictions_.store(0, std::memory_order_relaxed); - } - -private: - enum class ListId { - recent, - frequent, - recentGhost, - frequentGhost, - }; - - struct ResidentNode { - Key key; - Value value; - ListId list; - - template - ResidentNode(K&& k, V&& v, ListId listId) - : key(std::forward(k)) - , value(std::forward(v)) - , list(listId) - { - } - }; - - struct GhostNode { - Key key; - ListId list; - - template - GhostNode(K&& k, ListId listId) - : key(std::forward(k)) - , list(listId) - { - } - }; - - using ResidentList = std::list; - using ResidentIterator = typename ResidentList::iterator; - using ResidentIndex = std::unordered_map; - using ResidentIndexIterator = typename ResidentIndex::iterator; - - using GhostList = std::list; - using GhostIterator = typename GhostList::iterator; - using GhostIndex = std::unordered_map; - using GhostIndexIterator = typename GhostIndex::iterator; - -private: - ResidentList& residentListFor(ListId list) - { - return list == ListId::frequent ? frequent_ : recent_; - } - - GhostList& ghostListFor(ListId list) - { - return list == ListId::frequentGhost ? frequentGhost_ : recentGhost_; - } - - void promoteToFrequent(ResidentIterator node) - { - if (node->list == ListId::recent) { - frequent_.splice(frequent_.begin(), recent_, node); - node->list = ListId::frequent; - return; - } - - frequent_.splice(frequent_.begin(), frequent_, node); - } - - void adaptTarget(bool frequentGhostHit) - { - if (frequentGhostHit) { - const std::size_t divisor = - std::max(1, frequentGhost_.size()); - - const std::size_t delta = - std::max(1, recentGhost_.size() / divisor); - - recentTarget_ = delta >= recentTarget_ ? 0 : recentTarget_ - delta; - return; - } - - const std::size_t divisor = - std::max(1, recentGhost_.size()); - - const std::size_t delta = - std::max(1, frequentGhost_.size() / divisor); - - recentTarget_ = std::min(capacity_, recentTarget_ + delta); - } - - bool replace(bool frequentGhostHit) - { - if (residentIndex_.empty()) { - return false; - } - - if (!recent_.empty() - && (recent_.size() > recentTarget_ - || (frequentGhostHit && recent_.size() == recentTarget_))) { - return moveResidentLruToGhost(ListId::recent, ListId::recentGhost); - } - - if (!frequent_.empty()) { - return moveResidentLruToGhost(ListId::frequent, ListId::frequentGhost); - } - - if (!recent_.empty()) { - return moveResidentLruToGhost(ListId::recent, ListId::recentGhost); - } - - return false; - } - - bool moveResidentLruToGhost(ListId residentList, ListId ghostList) - { - auto& source = residentListFor(residentList); - if (source.empty()) { - return false; - } - - auto victim = std::prev(source.end()); - auto& destination = ghostListFor(ghostList); - - destination.emplace_front(victim->key, ghostList); - auto ghostNode = destination.begin(); - - try { - ghostIndex_.emplace(ghostNode->key, ghostNode); - } catch (...) { - destination.pop_front(); - throw; - } - - residentIndex_.erase(victim->key); - source.erase(victim); - - evictions_.fetch_add(1, std::memory_order_relaxed); - return true; - } - - template - bool insertCold(K&& key, V&& value) - { - bool evicted = false; - - const std::size_t recentSide = recent_.size() + recentGhost_.size(); - - if (recentSide == capacity_) { - if (recent_.size() < capacity_) { - removeGhostLru(ListId::recentGhost); - evicted = replace(false); - } else { - evicted = removeResidentLru(ListId::recent); - } - } else { - const std::size_t tracked = - residentIndex_.size() + ghostIndex_.size(); - - if (tracked >= capacity_) { - if (tracked >= 2 * capacity_) { - if (!removeGhostLru(ListId::frequentGhost)) { - removeGhostLru(ListId::recentGhost); - } - } - - evicted = replace(false); - } - } - - insertResident(std::forward(key), std::forward(value), ListId::recent); - - return evicted; - } - - template - void insertResident(K&& key, V&& value, ListId list) - { - auto& destination = residentListFor(list); - - destination.emplace_front(std::forward(key), std::forward(value), list); - auto node = destination.begin(); - - try { - auto insertion = residentIndex_.emplace(node->key, node); - - if (!insertion.second) { - insertion.first->second->value = std::move(node->value); - destination.pop_front(); - promoteToFrequent(insertion.first->second); - } - } catch (...) { - destination.pop_front(); - throw; - } - } - - bool removeResidentLru(ListId list) - { - auto& source = residentListFor(list); - if (source.empty()) { - return false; - } - - auto victim = std::prev(source.end()); - - residentIndex_.erase(victim->key); - source.erase(victim); - - evictions_.fetch_add(1, std::memory_order_relaxed); - return true; - } - - bool removeGhostLru(ListId list) - { - auto& source = ghostListFor(list); - if (source.empty()) { - return false; - } - - auto victim = std::prev(source.end()); - - ghostIndex_.erase(victim->key); - source.erase(victim); - - return true; - } - - void eraseResident(ResidentIndexIterator resident) - { - auto node = resident->second; - - residentListFor(node->list).erase(node); - residentIndex_.erase(resident); - } - - void eraseGhost(GhostIndexIterator ghost) - { - auto node = ghost->second; - - ghostListFor(node->list).erase(node); - ghostIndex_.erase(ghost); - } - - void trimGhostHistory() - { - while (ghostIndex_.size() > capacity_) { - if (!removeGhostLru(ListId::frequentGhost)) { - removeGhostLru(ListId::recentGhost); - } - } - } - -private: - const std::size_t capacity_; - std::size_t recentTarget_{0}; - - ResidentList recent_; - ResidentList frequent_; - GhostList recentGhost_; - GhostList frequentGhost_; - - ResidentIndex residentIndex_; - GhostIndex ghostIndex_; - - mutable std::mutex mutex_; - - std::atomic hits_{0}; - std::atomic misses_{0}; - std::atomic evictions_{0}; -}; - -// ============================================================================ -// 2. 分片 ARC 缓存 -// -// 设计目标: -// 1. 将全局大锁拆成多把 shard 小锁 -// 2. 每个 shard 内部是完整 ARC -// 3. 整体是分片近似 ARC,不是全局严格 ARC -// 4. 保留统一统计接口 -// ============================================================================ -template < - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to> -class ShardedARCCache final : public CachePolicy { -public: - explicit ShardedARCCache( - std::size_t capacity, - std::size_t shardCount = std::thread::hardware_concurrency(), - const Hash& hash = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : capacity_(capacity) - , hash_(hash) - , shardCount_(normalizeShardCount(capacity, shardCount)) - { - shards_.reserve(shardCount_); - - const std::size_t baseCapacity = capacity_ / shardCount_; - const std::size_t extraCapacity = capacity_ % shardCount_; - - for (std::size_t index = 0; index < shardCount_; ++index) { - const std::size_t shardCapacity = - baseCapacity + (index < extraCapacity ? 1U : 0U); - - shards_.push_back( - std::make_unique(shardCapacity, hash, equal)); - } - } - - ~ShardedARCCache() override = default; - - ShardedARCCache(const ShardedARCCache&) = delete; - ShardedARCCache& operator=(const ShardedARCCache&) = delete; - - ShardedARCCache(ShardedARCCache&&) = delete; - ShardedARCCache& operator=(ShardedARCCache&&) = delete; - - void put(const Key& key, const Value& value) override - { - shardFor(key).put(key, value); - } - - bool get(const Key& key, Value& value) override - { - return shardFor(key).get(key, value); - } - - using CachePolicy::get; - - [[nodiscard]] bool peek(const Key& key, Value& value) const override - { - return shardFor(key).peek(key, value); - } - - bool erase(const Key& key) override - { - return shardFor(key).erase(key); - } - - void clear() override - { - for (auto& shard : shards_) { - shard->clear(); - } - } - - void purge() - { - clear(); - } - - [[nodiscard]] bool contains(const Key& key) const override - { - return shardFor(key).contains(key); - } - - [[nodiscard]] bool isGhost(const Key& key) const - { - return shardFor(key).isGhost(key); - } - - [[nodiscard]] std::size_t size() const override - { - std::size_t total = 0; - - for (const auto& shard : shards_) { - total += shard->size(); - } - - return total; - } - - [[nodiscard]] std::size_t capacity() const noexcept override - { - return capacity_; - } - - [[nodiscard]] std::size_t shardCount() const noexcept - { - return shardCount_; - } - - [[nodiscard]] CacheStats stats() const noexcept override - { - CacheStats total; - - for (const auto& shard : shards_) { - const CacheStats shardStats = shard->stats(); - - total.hits += shardStats.hits; - total.misses += shardStats.misses; - total.evictions += shardStats.evictions; - } - - return total; - } - - void resetStats() noexcept override - { - for (auto& shard : shards_) { - shard->resetStats(); - } - } - - [[nodiscard]] std::vector shardStates() const - { - std::vector states; - states.reserve(shards_.size()); - - for (const auto& shard : shards_) { - states.push_back(shard->state()); - } - - return states; - } - -private: - using Shard = ARCCache; - -private: - static std::size_t normalizeShardCount( - std::size_t capacity, - std::size_t requestedShardCount) noexcept - { - if (capacity == 0) { - return 1; - } - - if (requestedShardCount == 0) { - requestedShardCount = 1; - } - - return std::min(capacity, requestedShardCount); - } - - [[nodiscard]] std::size_t shardIndex(const Key& key) const - { - return hash_(key) % shardCount_; - } - - Shard& shardFor(const Key& key) - { - return *shards_[shardIndex(key)]; - } - - const Shard& shardFor(const Key& key) const - { - return *shards_[shardIndex(key)]; - } - -private: - const std::size_t capacity_; - const Hash hash_; - const std::size_t shardCount_; - - std::vector> shards_; -}; - -template -using KArcCache = ARCCache; - -template -using KHashArcCache = ShardedARCCache; - -} // namespace KamaCache diff --git a/.cache_benchmark_build/include/CachePolicy.h b/.cache_benchmark_build/include/CachePolicy.h deleted file mode 100644 index 35ba715..0000000 --- a/.cache_benchmark_build/include/CachePolicy.h +++ /dev/null @@ -1,221 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace KamaCache { - -// ============================================================================ -// 1. 写入结果 -// ============================================================================ -enum class CacheWriteResult { - ignored, // 容量为 0,或策略决定忽略本次写入 - inserted, // 新插入,未发生淘汰 - updated, // key 已存在,更新 value - insertedWithEviction, // 新插入,并触发淘汰 -}; - -// ============================================================================ -// 2. 缓存统计信息 -// ============================================================================ -struct CacheStats { - std::uint64_t hits{0}; - std::uint64_t misses{0}; - std::uint64_t evictions{0}; - - [[nodiscard]] std::uint64_t requests() const noexcept - { - return hits + misses; - } - - [[nodiscard]] bool empty() const noexcept - { - return requests() == 0 && evictions == 0; - } - - [[nodiscard]] double hitRate() const noexcept - { - const std::uint64_t total = requests(); - - return total == 0 - ? 0.0 - : static_cast(hits) / static_cast(total); - } - - [[nodiscard]] double missRate() const noexcept - { - const std::uint64_t total = requests(); - - return total == 0 - ? 0.0 - : static_cast(misses) / static_cast(total); - } - - void reset() noexcept - { - hits = 0; - misses = 0; - evictions = 0; - } -}; - -// ============================================================================ -// 3. 缓存策略基类 -// -// 设计原则: -// 1. get(key, value) 是核心接口,用 bool 表达是否命中。 -// 2. tryGet(key) 是安全便捷接口,用 optional 避免 miss 和默认值混淆。 -// 3. getOrDefault(key, defaultValue) 用于业务允许默认值兜底的场景。 -// 4. peek(key, value) 默认不提供真实实现,具体缓存策略可覆盖。 -// 5. stats/resetStats 提供统一监控接口,具体缓存策略可覆盖。 -// ============================================================================ -template -class CachePolicy { -public: - virtual ~CachePolicy() = default; - - CachePolicy() = default; - - CachePolicy(const CachePolicy&) = delete; - CachePolicy& operator=(const CachePolicy&) = delete; - - CachePolicy(CachePolicy&&) = delete; - CachePolicy& operator=(CachePolicy&&) = delete; - - // ------------------------------------------------------------------------ - // 写入接口 - // - // 注意: - // 由于这是虚函数接口,不能做完美转发版本: - // template put(K&&, V&&) - // - // 如果具体缓存实现需要移动语义,可以在派生类中额外提供模板 put。 - // ------------------------------------------------------------------------ - virtual void put(const Key& key, const Value& value) = 0; - - // ------------------------------------------------------------------------ - // 查询接口 - // - // 命中返回 true,并通过 value 输出结果。 - // 未命中返回 false。 - // - // 对 LRU / LFU 来说,get 通常会改变缓存状态: - // LRU:刷新访问顺序 - // LFU:增加访问频率 - // ------------------------------------------------------------------------ - virtual bool get(const Key& key, Value& value) = 0; - - // ------------------------------------------------------------------------ - // 安全便捷查询接口 - // - // 推荐业务代码优先使用 tryGet,而不是 get(key) 返回默认值。 - // ------------------------------------------------------------------------ - [[nodiscard]] std::optional tryGet(const Key& key) - { - Value value{}; - - if (!get(key, value)) { - return std::nullopt; - } - - return value; - } - - // ------------------------------------------------------------------------ - // 默认值兜底查询接口 - // - // 适合业务明确允许默认值的场景。 - // ------------------------------------------------------------------------ - [[nodiscard]] Value getOrDefault(const Key& key, Value defaultValue = Value{}) - { - Value value{}; - - if (!get(key, value)) { - return defaultValue; - } - - return value; - } - - // ------------------------------------------------------------------------ - // 只读查看接口 - // - // peek 和 get 的区别: - // get 可能改变缓存状态; - // peek 不应该改变缓存状态。 - // - // 默认实现返回 false。 - // 支持 peek 的缓存策略应覆盖该方法。 - // ------------------------------------------------------------------------ - [[nodiscard]] virtual bool peek(const Key& key, Value& value) const - { - static_cast(key); - static_cast(value); - return false; - } - - [[nodiscard]] std::optional tryPeek(const Key& key) const - { - Value value{}; - - if (!peek(key, value)) { - return std::nullopt; - } - - return value; - } - - // ------------------------------------------------------------------------ - // 删除与清理 - // ------------------------------------------------------------------------ - virtual bool erase(const Key& key) = 0; - - virtual void clear() = 0; - - void purge() - { - clear(); - } - - // ------------------------------------------------------------------------ - // 状态查询 - // ------------------------------------------------------------------------ - [[nodiscard]] virtual bool contains(const Key& key) const = 0; - - [[nodiscard]] virtual std::size_t size() const = 0; - - [[nodiscard]] virtual std::size_t capacity() const noexcept = 0; - - [[nodiscard]] bool empty() const - { - return size() == 0; - } - - // ------------------------------------------------------------------------ - // 统计接口 - // - // 普通缓存可以不覆盖,默认返回空统计。 - // 分片缓存、生产级缓存建议覆盖。 - // ------------------------------------------------------------------------ - [[nodiscard]] virtual CacheStats stats() const noexcept - { - return CacheStats{}; - } - - virtual void resetStats() noexcept - { - } -}; - -// ============================================================================ -// 4. 兼容旧命名 -// ============================================================================ -template -using KICachePolicy = CachePolicy; - -template -using ICachePolicy = CachePolicy; - -} // namespace KamaCache \ No newline at end of file diff --git a/.cache_benchmark_build/include/LFU.h b/.cache_benchmark_build/include/LFU.h deleted file mode 100644 index 97b7539..0000000 --- a/.cache_benchmark_build/include/LFU.h +++ /dev/null @@ -1,590 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CachePolicy.h" - -namespace KamaCache { - -// ============================================================================ -// 1. 单分片 LFU 缓存 -// 设计目标: -// 1. O(1) 查找 -// 2. O(1) 频次提升 -// 3. O(1) 淘汰最低频率节点 -// 4. 同频率下按 LRU 淘汰 -// -// 数据结构: -// 1. frequencyBuckets_: -// frequency -> list -// -// 2. nodeIndex_: -// key -> list::iterator -// -// 说明: -// CacheNode 内部同时保存 key / value / frequency。 -// 这样可以避免 value、frequency、bucket key 分散在多个容器中造成状态不一致。 -// ============================================================================ -template < - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to> -class alignas(64) LFUCache final : public CachePolicy { -public: - explicit LFUCache( - std::size_t capacity, - std::size_t maxFrequency = defaultMaxFrequency()) - : LFUCache(capacity, maxFrequency, Hash{}, KeyEqual{}) - { - } - - LFUCache( - std::size_t capacity, - std::size_t maxFrequency, - const Hash& hash, - const KeyEqual& equal) - : capacity_(capacity) - , maxFrequency_(std::max(2, maxFrequency)) - , nodeIndex_(0, hash, equal) - { - nodeIndex_.reserve(capacity_); - frequencyBuckets_.reserve(capacity_); - } - - ~LFUCache() override = default; - - LFUCache(const LFUCache&) = delete; - LFUCache& operator=(const LFUCache&) = delete; - - LFUCache(LFUCache&&) = delete; - LFUCache& operator=(LFUCache&&) = delete; - - void put(const Key& key, const Value& value) override - { - static_cast(putAndReport(key, value)); - } - - template - CacheWriteResult putAndReport(K&& key, V&& value) - { - if (capacity_ == 0) { - return CacheWriteResult::ignored; - } - - std::lock_guard lock(mutex_); - - auto found = nodeIndex_.find(key); - if (found != nodeIndex_.end()) { - found->second->value = std::forward(value); - promote(found->second); - return CacheWriteResult::updated; - } - - const bool needsEviction = nodeIndex_.size() >= capacity_; - - auto& bucket = frequencyBuckets_[1]; - bucket.emplace_front(std::forward(key), std::forward(value), 1); - - auto insertedNode = bucket.begin(); - - try { - auto insertion = nodeIndex_.emplace(insertedNode->key, insertedNode); - if (!insertion.second) { - insertion.first->second->value = insertedNode->value; - - bucket.erase(insertedNode); - if (bucket.empty()) { - frequencyBuckets_.erase(1); - } - - promote(insertion.first->second); - return CacheWriteResult::updated; - } - } catch (...) { - bucket.erase(insertedNode); - if (bucket.empty()) { - frequencyBuckets_.erase(1); - } - throw; - } - - if (needsEviction) { - evictOneLocked(); - } - - minFrequency_ = 1; - - return needsEviction - ? CacheWriteResult::insertedWithEviction - : CacheWriteResult::inserted; - } - - bool get(const Key& key, Value& value) override - { - std::lock_guard lock(mutex_); - - auto found = nodeIndex_.find(key); - if (found == nodeIndex_.end()) { - return false; - } - - value = found->second->value; - promote(found->second); - - return true; - } - - using CachePolicy::get; - - [[nodiscard]] bool peek(const Key& key, Value& value) const override - { - std::lock_guard lock(mutex_); - - const auto found = nodeIndex_.find(key); - if (found == nodeIndex_.end()) { - return false; - } - - value = found->second->value; - return true; - } - - bool erase(const Key& key) override - { - std::lock_guard lock(mutex_); - - auto found = nodeIndex_.find(key); - if (found == nodeIndex_.end()) { - return false; - } - - const std::size_t removedFrequency = found->second->frequency; - - eraseNodeFromBucket(found->second); - nodeIndex_.erase(found); - - if (nodeIndex_.empty()) { - minFrequency_ = 0; - } else if ( - removedFrequency == minFrequency_ - && frequencyBuckets_.find(removedFrequency) == frequencyBuckets_.end()) { - recomputeMinFrequency(); - } - - return true; - } - - void clear() override - { - std::lock_guard lock(mutex_); - - nodeIndex_.clear(); - frequencyBuckets_.clear(); - minFrequency_ = 0; - } - - void purge() - { - clear(); - } - - [[nodiscard]] bool contains(const Key& key) const override - { - std::lock_guard lock(mutex_); - return nodeIndex_.find(key) != nodeIndex_.end(); - } - - [[nodiscard]] std::size_t size() const override - { - std::lock_guard lock(mutex_); - return nodeIndex_.size(); - } - - [[nodiscard]] std::size_t capacity() const noexcept override - { - return capacity_; - } - - [[nodiscard]] std::size_t maxFrequency() const noexcept - { - return maxFrequency_; - } - - static constexpr std::size_t defaultMaxFrequency() noexcept - { - return 1U << 20U; - } - -private: - struct CacheNode { - Key key; - Value value; - std::size_t frequency; - - template - CacheNode(K&& k, V&& v, std::size_t f) - : key(std::forward(k)) - , value(std::forward(v)) - , frequency(f) - { - } - }; - - using NodeList = std::list; - using NodeIterator = typename NodeList::iterator; - - using NodeIndex = std::unordered_map; - using FrequencyBuckets = std::unordered_map; - -private: - void promote(NodeIterator node) - { - if (node->frequency >= maxFrequency_) { - ageFrequenciesLocked(); - } - - const std::size_t oldFrequency = node->frequency; - const std::size_t newFrequency = oldFrequency + 1; - - auto oldBucketIt = frequencyBuckets_.find(oldFrequency); - auto& oldBucket = oldBucketIt->second; - - auto newBucketIt = frequencyBuckets_.try_emplace(newFrequency).first; - auto& newBucket = newBucketIt->second; - - newBucket.splice(newBucket.begin(), oldBucket, node); - node->frequency = newFrequency; - - if (oldBucket.empty()) { - frequencyBuckets_.erase(oldFrequency); - - if (minFrequency_ == oldFrequency) { - minFrequency_ = newFrequency; - } - } - } - - void eraseNodeFromBucket(NodeIterator node) - { - const std::size_t frequency = node->frequency; - - auto bucketIt = frequencyBuckets_.find(frequency); - bucketIt->second.erase(node); - - if (bucketIt->second.empty()) { - frequencyBuckets_.erase(bucketIt); - } - } - - void evictOneLocked() - { - if (nodeIndex_.empty()) { - minFrequency_ = 0; - return; - } - - auto bucketIt = frequencyBuckets_.find(minFrequency_); - - if (bucketIt == frequencyBuckets_.end() || bucketIt->second.empty()) { - recomputeMinFrequency(); - bucketIt = frequencyBuckets_.find(minFrequency_); - } - - auto& bucket = bucketIt->second; - - // 同频率下,链表头部是最近访问,尾部是最久未访问。 - auto victim = std::prev(bucket.end()); - - nodeIndex_.erase(victim->key); - bucket.erase(victim); - - if (bucket.empty()) { - frequencyBuckets_.erase(bucketIt); - } - - if (nodeIndex_.empty()) { - minFrequency_ = 0; - } - } - - void ageFrequenciesLocked() - { - if (frequencyBuckets_.empty()) { - minFrequency_ = 0; - return; - } - - std::vector frequencies; - frequencies.reserve(frequencyBuckets_.size()); - - for (const auto& bucketPair : frequencyBuckets_) { - frequencies.push_back(bucketPair.first); - } - - std::sort(frequencies.begin(), frequencies.end()); - - FrequencyBuckets agedBuckets; - agedBuckets.reserve(frequencyBuckets_.size()); - - // 先创建目标桶,降低迁移中途异常导致状态不一致的风险。 - for (const std::size_t frequency : frequencies) { - const std::size_t agedFrequency = - std::max(1, frequency / 2); - - agedBuckets.try_emplace(agedFrequency); - } - - for (const std::size_t frequency : frequencies) { - auto sourceIt = frequencyBuckets_.find(frequency); - if (sourceIt == frequencyBuckets_.end()) { - continue; - } - - const std::size_t agedFrequency = - std::max(1, frequency / 2); - - auto& source = sourceIt->second; - auto& destination = agedBuckets.find(agedFrequency)->second; - - // 从旧桶尾部搬到新桶头部,可以保持原有 MRU -> LRU 的相对顺序。 - while (!source.empty()) { - auto node = std::prev(source.end()); - - node->frequency = agedFrequency; - destination.splice(destination.begin(), source, node); - } - } - - frequencyBuckets_.swap(agedBuckets); - recomputeMinFrequency(); - } - - void recomputeMinFrequency() - { - minFrequency_ = std::numeric_limits::max(); - - for (const auto& bucket : frequencyBuckets_) { - minFrequency_ = std::min(minFrequency_, bucket.first); - } - - if (frequencyBuckets_.empty()) { - minFrequency_ = 0; - } - } - -private: - const std::size_t capacity_; - const std::size_t maxFrequency_; - - std::size_t minFrequency_{0}; - - NodeIndex nodeIndex_; - FrequencyBuckets frequencyBuckets_; - - mutable std::mutex mutex_; -}; - -// ============================================================================ -// 2. 分片 LFU 缓存 -// 设计目标: -// 1. 将一把全局大锁拆成多把 shard 小锁 -// 2. 每个 shard 内部是精确 LFU -// 3. 整体是分片近似 LFU,不是全局严格 LFU -// 4. 保留命中、未命中、淘汰统计 -// ============================================================================ -template < - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to> -class ShardedLFUCache final : public CachePolicy { -public: - explicit ShardedLFUCache( - std::size_t capacity, - std::size_t shardCount = std::thread::hardware_concurrency(), - std::size_t maxFrequency = - LFUCache::defaultMaxFrequency(), - const Hash& hash = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : capacity_(capacity) - , hash_(hash) - , shardCount_(normalizeShardCount(capacity, shardCount)) - { - shards_.reserve(shardCount_); - - const std::size_t baseCapacity = capacity_ / shardCount_; - const std::size_t extraCapacity = capacity_ % shardCount_; - - for (std::size_t index = 0; index < shardCount_; ++index) { - const std::size_t shardCapacity = - baseCapacity + (index < extraCapacity ? 1U : 0U); - - shards_.push_back( - std::make_unique( - shardCapacity, - maxFrequency, - hash, - equal)); - } - } - - ~ShardedLFUCache() override = default; - - ShardedLFUCache(const ShardedLFUCache&) = delete; - ShardedLFUCache& operator=(const ShardedLFUCache&) = delete; - - ShardedLFUCache(ShardedLFUCache&&) = delete; - ShardedLFUCache& operator=(ShardedLFUCache&&) = delete; - - void put(const Key& key, const Value& value) override - { - const CacheWriteResult result = shardFor(key).putAndReport(key, value); - - if (result == CacheWriteResult::insertedWithEviction) { - evictions_.fetch_add(1, std::memory_order_relaxed); - } - } - - bool get(const Key& key, Value& value) override - { - if (shardFor(key).get(key, value)) { - hits_.fetch_add(1, std::memory_order_relaxed); - return true; - } - - misses_.fetch_add(1, std::memory_order_relaxed); - return false; - } - - using CachePolicy::get; - - [[nodiscard]] bool peek(const Key& key, Value& value) const override - { - return shardFor(key).peek(key, value); - } - - bool erase(const Key& key) override - { - return shardFor(key).erase(key); - } - - void clear() override - { - for (auto& shard : shards_) { - shard->clear(); - } - } - - void purge() - { - clear(); - } - - [[nodiscard]] bool contains(const Key& key) const override - { - return shardFor(key).contains(key); - } - - [[nodiscard]] std::size_t size() const override - { - std::size_t total = 0; - - for (const auto& shard : shards_) { - total += shard->size(); - } - - return total; - } - - [[nodiscard]] std::size_t capacity() const noexcept override - { - return capacity_; - } - - [[nodiscard]] std::size_t shardCount() const noexcept - { - return shardCount_; - } - - [[nodiscard]] CacheStats stats() const noexcept override - { - return CacheStats{ - hits_.load(std::memory_order_relaxed), - misses_.load(std::memory_order_relaxed), - evictions_.load(std::memory_order_relaxed), - }; - } - - void resetStats() noexcept override - { - hits_.store(0, std::memory_order_relaxed); - misses_.store(0, std::memory_order_relaxed); - evictions_.store(0, std::memory_order_relaxed); - } - -private: - using Shard = LFUCache; - -private: - static std::size_t normalizeShardCount( - std::size_t capacity, - std::size_t requestedShardCount) noexcept - { - if (capacity == 0) { - return 1; - } - - if (requestedShardCount == 0) { - requestedShardCount = 1; - } - - return std::min(capacity, requestedShardCount); - } - - [[nodiscard]] std::size_t shardIndex(const Key& key) const - { - return hash_(key) % shardCount_; - } - - Shard& shardFor(const Key& key) - { - return *shards_[shardIndex(key)]; - } - - const Shard& shardFor(const Key& key) const - { - return *shards_[shardIndex(key)]; - } - -private: - const std::size_t capacity_; - const Hash hash_; - const std::size_t shardCount_; - - std::vector> shards_; - - std::atomic hits_{0}; - std::atomic misses_{0}; - std::atomic evictions_{0}; -}; - -template -using KLfuCache = LFUCache; - -template -using KHashLfuCache = ShardedLFUCache; - -} // namespace KamaCache diff --git a/.cache_benchmark_build/include/LRU.h b/.cache_benchmark_build/include/LRU.h deleted file mode 100644 index 9978c81..0000000 --- a/.cache_benchmark_build/include/LRU.h +++ /dev/null @@ -1,691 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace KamaCache -{ - -// ============================================================================ -// 1. 单分片 LRU 缓存 -// 设计目标:O(1) 查找、O(1) 提权、O(1) 淘汰 -// 数据结构:std::list + std::unordered_map -// ============================================================================ -template< - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to -> -class alignas(64) KLruCacheShard -{ -public: - struct CacheNode { - Key key; - Value value; - - template - CacheNode(K&& k, V&& v) - : key(std::forward(k)) - , value(std::forward(v)) - {} - }; - - using ListType = std::list; - using ListIter = typename ListType::iterator; - using MapType = std::unordered_map; - -public: - explicit KLruCacheShard(std::size_t capacity) - : capacity_(capacity) - { - cacheMap_.reserve(capacity_); - } - - ~KLruCacheShard() = default; - - KLruCacheShard(const KLruCacheShard&) = delete; - KLruCacheShard& operator=(const KLruCacheShard&) = delete; - - KLruCacheShard(KLruCacheShard&&) = delete; - KLruCacheShard& operator=(KLruCacheShard&&) = delete; - - bool get(const Key& key, Value& value) - { - std::lock_guard lock(mutex_); - - auto it = cacheMap_.find(key); - if (it == cacheMap_.end()) { - return false; - } - - // LRU 语义:命中后移动到头部 - cacheList_.splice(cacheList_.begin(), cacheList_, it->second); - value = it->second->value; - - return true; - } - - bool peek(const Key& key, Value& value) const - { - std::lock_guard lock(mutex_); - - auto it = cacheMap_.find(key); - if (it == cacheMap_.end()) { - return false; - } - - // peek 只读 value,不改变 LRU 顺序 - value = it->second->value; - return true; - } - - template - bool updateIfExists(const Key& key, V&& value) - { - std::lock_guard lock(mutex_); - - auto it = cacheMap_.find(key); - if (it == cacheMap_.end()) { - return false; - } - - it->second->value = std::forward(value); - cacheList_.splice(cacheList_.begin(), cacheList_, it->second); - - return true; - } - - template - void put(K&& key, V&& value) - { - if (capacity_ == 0) { - return; - } - - std::lock_guard lock(mutex_); - - auto it = cacheMap_.find(key); - if (it != cacheMap_.end()) { - it->second->value = std::forward(value); - cacheList_.splice(cacheList_.begin(), cacheList_, it->second); - return; - } - - if (cacheList_.size() >= capacity_) { - auto& backNode = cacheList_.back(); - cacheMap_.erase(backNode.key); - cacheList_.pop_back(); - } - - cacheList_.emplace_front(std::forward(key), std::forward(value)); - - try { - cacheMap_.emplace(cacheList_.front().key, cacheList_.begin()); - } catch (...) { - cacheList_.pop_front(); - throw; - } - } - - bool remove(const Key& key) - { - std::lock_guard lock(mutex_); - - auto it = cacheMap_.find(key); - if (it == cacheMap_.end()) { - return false; - } - - cacheList_.erase(it->second); - cacheMap_.erase(it); - - return true; - } - - void clear() - { - std::lock_guard lock(mutex_); - cacheList_.clear(); - cacheMap_.clear(); - } - - std::size_t size() const - { - std::lock_guard lock(mutex_); - return cacheList_.size(); - } - - std::size_t capacity() const noexcept - { - return capacity_; - } - -private: - const std::size_t capacity_; - - ListType cacheList_; - MapType cacheMap_; - - mutable std::mutex mutex_; -}; - - -// ============================================================================ -// 2. 分片 LRU 缓存 -// 设计目标:将一把大锁拆成多把小锁,降低高并发访问下的锁竞争 -// 注意:这是分片加锁,不是 lock-free -// ============================================================================ -template< - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to -> -class KHighConcurrencyCache -{ -public: - explicit KHighConcurrencyCache(std::size_t totalCapacity, int shardNum = 0) - : shardNum_(normalizeShardNum(shardNum)) - { - const std::size_t shardCapacity = ceilDiv(totalCapacity, shardNum_); - - shards_.reserve(shardNum_); - - for (std::size_t i = 0; i < shardNum_; ++i) { - shards_.emplace_back( - std::make_unique(shardCapacity) - ); - } - } - - KHighConcurrencyCache(const KHighConcurrencyCache&) = delete; - KHighConcurrencyCache& operator=(const KHighConcurrencyCache&) = delete; - - template - void put(K&& key, V&& value) - { - const std::size_t index = getShardIndex(key); - shards_[index]->put(std::forward(key), std::forward(value)); - } - - bool get(const Key& key, Value& value) - { - return shards_[getShardIndex(key)]->get(key, value); - } - - bool peek(const Key& key, Value& value) const - { - return shards_[getShardIndex(key)]->peek(key, value); - } - - template - bool updateIfExists(const Key& key, V&& value) - { - return shards_[getShardIndex(key)]->updateIfExists( - key, - std::forward(value) - ); - } - - bool remove(const Key& key) - { - return shards_[getShardIndex(key)]->remove(key); - } - - void clear() - { - for (auto& shard : shards_) { - shard->clear(); - } - } - - std::size_t size() const - { - std::size_t total = 0; - - for (const auto& shard : shards_) { - total += shard->size(); - } - - return total; - } - - std::size_t shardNum() const noexcept - { - return shardNum_; - } - -private: - using ShardType = KLruCacheShard; - - static std::size_t normalizeShardNum(int shardNum) - { - if (shardNum > 0) { - return static_cast(shardNum); - } - - const unsigned int hardwareNum = std::thread::hardware_concurrency(); - - if (hardwareNum == 0) { - return 1; - } - - return static_cast(hardwareNum); - } - - static std::size_t ceilDiv(std::size_t a, std::size_t b) - { - return (a + b - 1) / b; - } - - std::size_t getShardIndex(const Key& key) const - { - return hash_(key) % shardNum_; - } - -private: - const std::size_t shardNum_; - - Hash hash_; - - std::vector> shards_; -}; - - -// ============================================================================ -// 3. LRU-K 历史区分片 -// 设计目标:把 count 和 value 放在同一个节点里,避免两个容器之间的数据不一致 -// ============================================================================ -enum class KHistoryAccessResult -{ - Miss, - HitHistory, - Promoted -}; - -template< - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to -> -class KHistoryShard -{ -public: - struct HistoryNode { - Key key; - Value value; - std::size_t count; - - template - HistoryNode(K&& k, V&& v, std::size_t c) - : key(std::forward(k)) - , value(std::forward(v)) - , count(c) - {} - }; - - using ListType = std::list; - using ListIter = typename ListType::iterator; - using MapType = std::unordered_map; - -public: - explicit KHistoryShard(std::size_t capacity) - : capacity_(capacity) - { - historyMap_.reserve(capacity_); - } - - KHistoryShard(const KHistoryShard&) = delete; - KHistoryShard& operator=(const KHistoryShard&) = delete; - - KHistoryShard(KHistoryShard&&) = delete; - KHistoryShard& operator=(KHistoryShard&&) = delete; - - KHistoryAccessResult get(const Key& key, Value& value, std::size_t k) - { - std::lock_guard lock(mutex_); - - auto it = historyMap_.find(key); - if (it == historyMap_.end()) { - return KHistoryAccessResult::Miss; - } - - HistoryNode& node = *(it->second); - - ++node.count; - value = node.value; - - if (node.count >= k) { - historyList_.erase(it->second); - historyMap_.erase(it); - return KHistoryAccessResult::Promoted; - } - - historyList_.splice(historyList_.begin(), historyList_, it->second); - return KHistoryAccessResult::HitHistory; - } - - template - std::optional put(const Key& key, V&& value, std::size_t k) - { - if (capacity_ == 0) { - return std::nullopt; - } - - std::lock_guard lock(mutex_); - - auto it = historyMap_.find(key); - if (it != historyMap_.end()) { - HistoryNode& node = *(it->second); - - node.value = std::forward(value); - ++node.count; - - if (node.count >= k) { - std::optional promotedValue = node.value; - - historyList_.erase(it->second); - historyMap_.erase(it); - - return promotedValue; - } - - historyList_.splice(historyList_.begin(), historyList_, it->second); - return std::nullopt; - } - - if (historyList_.size() >= capacity_) { - auto& backNode = historyList_.back(); - historyMap_.erase(backNode.key); - historyList_.pop_back(); - } - - historyList_.emplace_front(key, std::forward(value), 1); - - try { - historyMap_.emplace(historyList_.front().key, historyList_.begin()); - } catch (...) { - historyList_.pop_front(); - throw; - } - - return std::nullopt; - } - - bool remove(const Key& key) - { - std::lock_guard lock(mutex_); - - auto it = historyMap_.find(key); - if (it == historyMap_.end()) { - return false; - } - - historyList_.erase(it->second); - historyMap_.erase(it); - - return true; - } - - void clear() - { - std::lock_guard lock(mutex_); - historyList_.clear(); - historyMap_.clear(); - } - - std::size_t size() const - { - std::lock_guard lock(mutex_); - return historyList_.size(); - } - -private: - const std::size_t capacity_; - - ListType historyList_; - MapType historyMap_; - - mutable std::mutex mutex_; -}; - - -// ============================================================================ -// 4. 分片 LRU-K 历史缓存 -// ============================================================================ -template< - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to -> -class KShardedHistoryCache -{ -public: - explicit KShardedHistoryCache(std::size_t totalCapacity, int shardNum = 0) - : shardNum_(normalizeShardNum(shardNum)) - { - const std::size_t shardCapacity = ceilDiv(totalCapacity, shardNum_); - - shards_.reserve(shardNum_); - - for (std::size_t i = 0; i < shardNum_; ++i) { - shards_.emplace_back( - std::make_unique(shardCapacity) - ); - } - } - - KHistoryAccessResult get(const Key& key, Value& value, std::size_t k) - { - return shards_[getShardIndex(key)]->get(key, value, k); - } - - template - std::optional put(const Key& key, V&& value, std::size_t k) - { - return shards_[getShardIndex(key)]->put( - key, - std::forward(value), - k - ); - } - - bool remove(const Key& key) - { - return shards_[getShardIndex(key)]->remove(key); - } - - void clear() - { - for (auto& shard : shards_) { - shard->clear(); - } - } - - std::size_t size() const - { - std::size_t total = 0; - - for (const auto& shard : shards_) { - total += shard->size(); - } - - return total; - } - -private: - using ShardType = KHistoryShard; - - static std::size_t normalizeShardNum(int shardNum) - { - if (shardNum > 0) { - return static_cast(shardNum); - } - - const unsigned int hardwareNum = std::thread::hardware_concurrency(); - - if (hardwareNum == 0) { - return 1; - } - - return static_cast(hardwareNum); - } - - static std::size_t ceilDiv(std::size_t a, std::size_t b) - { - return (a + b - 1) / b; - } - - std::size_t getShardIndex(const Key& key) const - { - return hash_(key) % shardNum_; - } - -private: - const std::size_t shardNum_; - - Hash hash_; - - std::vector> shards_; -}; - - -// ============================================================================ -// 5. 升级版 LRU-K 缓存 -// 设计目标: -// 1. 主缓存只放热点数据 -// 2. 历史区记录未达到 K 次访问的数据 -// 3. get / put 都参与访问计数 -// 4. count 和 value 在同一个历史节点中维护,避免跨容器不一致 -// ============================================================================ -template< - typename Key, - typename Value, - typename Hash = std::hash, - typename KeyEqual = std::equal_to -> -class KLruKCache -{ -public: - KLruKCache( - std::size_t capacity, - std::size_t historyCapacity, - std::size_t k, - int shardNum = 0 - ) - : k_(checkK(k)) - , mainCache_(capacity, shardNum) - , historyCache_(historyCapacity, shardNum) - { - if (k_ > 1 && historyCapacity == 0) { - throw std::invalid_argument( - "historyCapacity must be greater than 0 when k > 1" - ); - } - } - - KLruKCache(const KLruKCache&) = delete; - KLruKCache& operator=(const KLruKCache&) = delete; - - bool get(const Key& key, Value& value) - { - if (mainCache_.get(key, value)) { - return true; - } - - if (k_ == 1) { - return false; - } - - const KHistoryAccessResult result = historyCache_.get(key, value, k_); - - if (result == KHistoryAccessResult::Miss) { - return false; - } - - if (result == KHistoryAccessResult::Promoted) { - mainCache_.put(key, value); - } - - return true; - } - - template - void put(const Key& key, V&& value) - { - if (mainCache_.updateIfExists(key, value)) { - return; - } - - if (k_ == 1) { - mainCache_.put(key, std::forward(value)); - return; - } - - std::optional promotedValue = - historyCache_.put(key, std::forward(value), k_); - - if (promotedValue.has_value()) { - mainCache_.put(key, std::move(*promotedValue)); - } - } - - bool remove(const Key& key) - { - const bool removedFromMain = mainCache_.remove(key); - const bool removedFromHistory = historyCache_.remove(key); - - return removedFromMain || removedFromHistory; - } - - void clear() - { - mainCache_.clear(); - historyCache_.clear(); - } - - std::size_t mainSize() const - { - return mainCache_.size(); - } - - std::size_t historySize() const - { - return historyCache_.size(); - } - - std::size_t k() const noexcept - { - return k_; - } - -private: - static std::size_t checkK(std::size_t k) - { - if (k == 0) { - throw std::invalid_argument("k must be greater than 0"); - } - - return k; - } - -private: - const std::size_t k_; - - KHighConcurrencyCache mainCache_; - KShardedHistoryCache historyCache_; -}; - -} // namespace KamaCache \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..36abd0a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + compiler: [gcc, clang] + + steps: + - uses: actions/checkout@v4 + + - name: Configure + env: + CXX: ${{ matrix.compiler == 'gcc' && 'g++' || 'clang++' }} + run: > + cmake -S . -B build + -DCMAKE_BUILD_TYPE=Debug + -DCONCURRENT_CACHE_ENABLE_SANITIZERS=ON + + - name: Build + run: cmake --build build --parallel 2 + + - name: Test + run: ctest --test-dir build --output-on-failure + + - name: Smoke benchmark + run: ./run_cache_benchmark.sh . 10000 256 2 2 diff --git a/.gitignore b/.gitignore index 9b1646e..38503cb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ # C/C++ build files build/ +build-*/ cmake-build-*/ +.cache_benchmark_build/ *.o *.out *.a diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..415aba0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.16) + +project(ConcurrentCacheSystem VERSION 0.1.0 LANGUAGES CXX) + +option(CONCURRENT_CACHE_BUILD_TESTS "Build the test executable" ON) +option(CONCURRENT_CACHE_ENABLE_SANITIZERS "Enable AddressSanitizer and UBSan" OFF) + +add_library(concurrent_cache INTERFACE) +add_library(ConcurrentCacheSystem::concurrent_cache ALIAS concurrent_cache) + +target_include_directories( + concurrent_cache + INTERFACE + $ + $ +) +target_compile_features(concurrent_cache INTERFACE cxx_std_17) + +if(CONCURRENT_CACHE_BUILD_TESTS) + enable_testing() + find_package(Threads REQUIRED) + + add_executable(cache_tests tests/cache_tests.cpp) + target_link_libraries(cache_tests PRIVATE concurrent_cache Threads::Threads) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(cache_tests PRIVATE -Wall -Wextra -Wpedantic -Werror) + endif() + + if(CONCURRENT_CACHE_ENABLE_SANITIZERS) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + message(FATAL_ERROR "Sanitizers require Clang or GCC") + endif() + target_compile_options( + cache_tests PRIVATE + -fsanitize=address,undefined + -fno-omit-frame-pointer + ) + target_link_options( + cache_tests PRIVATE + -fsanitize=address,undefined + -fno-omit-frame-pointer + ) + endif() + + add_test(NAME cache_tests COMMAND cache_tests) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..37ccf4f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 FILWYZ + +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. diff --git a/LRU.h b/LRU.h index 5ecea93..752f5e9 100644 --- a/LRU.h +++ b/LRU.h @@ -1,6 +1,9 @@ #pragma once +#include +#include #include +#include #include #include #include @@ -12,7 +15,9 @@ #include #include -namespace Cachae +#include "CachePolicy.h" + +namespace Cache { // ============================================================================ @@ -98,7 +103,7 @@ class alignas(64) LruCacheShard } template - bool updateifExists(K&& key, V&& value) + bool updateIfExists(K&& key, V&& value) { std::lock_guard lock(mutex_); @@ -112,10 +117,10 @@ class alignas(64) LruCacheShard } template - void put(K&& key, V&& value) + CacheWriteResult put(K&& key, V&& value) { if (capacity_ == 0) { - return; + return CacheWriteResult::ignored; } std::lock_guard lock(mutex_); @@ -124,10 +129,11 @@ class alignas(64) LruCacheShard if (it != cacheMap_.end()) { it->second->value = std::forward(value); cacheList_.splice(cacheList_.begin(), cacheList_, it->second); - return; + return CacheWriteResult::updated; } - if (cacheMap_.size() >= capacity_) { + const bool needsEviction = cacheMap_.size() >= capacity_; + if (needsEviction) { auto& backNode = cacheList_.back(); cacheMap_.erase(backNode.key); cacheList_.pop_back(); @@ -141,6 +147,10 @@ class alignas(64) LruCacheShard cacheList_.pop_front(); throw; } + + return needsEviction + ? CacheWriteResult::insertedWithEviction + : CacheWriteResult::inserted; } bool remove(const Key& key) @@ -176,6 +186,12 @@ class alignas(64) LruCacheShard { return capacity_; } + + bool contains(const Key& key) const + { + std::lock_guard lock(mutex_); + return cacheMap_.find(key) != cacheMap_.end(); + } }; @@ -190,33 +206,31 @@ template< typename Hash = std::hash, typename KeyEqual = std::equal_to > -class HighConcurrencyCache +class HighConcurrencyCache final : public CachePolicy { private: using ShardType = LruCacheShard; + const std::size_t capacity_; const std::size_t shardNum_; Hash hash_; std::vector> shards_; + std::atomic hits_{0}; + std::atomic misses_{0}; + std::atomic evictions_{0}; private: - static std::size_t normalizeShardNum(int shardNum) + static std::size_t normalizeShardNum(std::size_t capacity, int shardNum) { + std::size_t normalized = 0; if (shardNum > 0) { - return static_cast(shardNum); - } - - const unsigned int hardwareNum = std::thread::hardware_concurrency(); - if (hardwareNum == 0) { - return 1; + normalized = static_cast(shardNum); + } else { + normalized = static_cast(std::thread::hardware_concurrency()); } - return static_cast(hardwareNum); - } - - static std::size_t ceilDiv(std::size_t a, std::size_t b) - { - return (a + b - 1) / b; + normalized = std::max(1, normalized); + return capacity == 0 ? 1 : std::min(capacity, normalized); } std::size_t getShardIndex(const Key& key) const @@ -226,14 +240,18 @@ class HighConcurrencyCache public: explicit HighConcurrencyCache(std::size_t totalCapacity, int shardNum = 0) - : shardNum_(normalizeShardNum(shardNum)) + : capacity_(totalCapacity) + , shardNum_(normalizeShardNum(totalCapacity, shardNum)) { - const std::size_t shardCapacity = ceilDiv(totalCapacity, shardNum_); shards_.reserve(shardNum_); + const std::size_t baseCapacity = capacity_ / shardNum_; + const std::size_t extraCapacity = capacity_ % shardNum_; for (std::size_t i = 0; i < shardNum_; ++i) { shards_.emplace_back( - std::make_unique(shardCapacity) + std::make_unique( + baseCapacity + (i < extraCapacity ? 1U : 0U) + ) ); } } @@ -243,44 +261,64 @@ class HighConcurrencyCache HighConcurrencyCache(HighConcurrencyCache&&) = default; HighConcurrencyCache& operator=(HighConcurrencyCache&&) = default; + void put(const Key& key, const Value& value) override + { + putImpl(key, value); + } + template void put(K&& key, V&& value) { - // 先通过 key 引用计算出 index,再完美转发,避免生命周期提前结束 - const std::size_t index = getShardIndex(key); - shards_[index]->put(std::forward(key), std::forward(value)); + putImpl(std::forward(key), std::forward(value)); } - bool get(const Key& key, Value& value) + bool get(const Key& key, Value& value) override { - return shards_[getShardIndex(key)]->get(key, value); + if (shards_[getShardIndex(key)]->get(key, value)) { + hits_.fetch_add(1, std::memory_order_relaxed); + return true; + } + misses_.fetch_add(1, std::memory_order_relaxed); + return false; } - bool peek(const Key& key, Value& value) const + using CachePolicy::get; + + bool peek(const Key& key, Value& value) const override { return shards_[getShardIndex(key)]->peek(key, value); } template - bool updateifExists(K&& key, V&& value) + bool updateIfExists(K&& key, V&& value) { const std::size_t index = getShardIndex(key); - return shards_[index]->updateifExists(std::forward(key), std::forward(value)); + return shards_[index]->updateIfExists(std::forward(key), std::forward(value)); } - bool remove(const Key& key) + bool erase(const Key& key) override { return shards_[getShardIndex(key)]->remove(key); } - void clear() + bool remove(const Key& key) + { + return erase(key); + } + + void clear() override { for (auto& shard : shards_) { shard->clear(); } } - std::size_t size() const + bool contains(const Key& key) const override + { + return shards_[getShardIndex(key)]->contains(key); + } + + std::size_t size() const override { std::size_t total = 0; for (const auto& shard : shards_) { @@ -293,6 +331,39 @@ class HighConcurrencyCache { return shardNum_; } + + std::size_t capacity() const noexcept override + { + return capacity_; + } + + CacheStats stats() const noexcept override + { + return CacheStats{ + hits_.load(std::memory_order_relaxed), + misses_.load(std::memory_order_relaxed), + evictions_.load(std::memory_order_relaxed), + }; + } + + void resetStats() noexcept override + { + hits_.store(0, std::memory_order_relaxed); + misses_.store(0, std::memory_order_relaxed); + evictions_.store(0, std::memory_order_relaxed); + } + +private: + template + void putImpl(K&& key, V&& value) + { + const std::size_t index = getShardIndex(key); + if (shards_[index]->put( + std::forward(key), + std::forward(value)) == CacheWriteResult::insertedWithEviction) { + evictions_.fetch_add(1, std::memory_order_relaxed); + } + } }; @@ -622,8 +693,7 @@ class LruKCache template void put(K&& key, V&& value) { - // 关键修复:方法名大小写统一调整为 updateifExists,并修正生命周期引用 - if (mainCache_.updateifExists(key, value)) { + if (mainCache_.updateIfExists(key, value)) { return; } @@ -670,4 +740,4 @@ class LruKCache } }; -} // namespace Cachae \ No newline at end of file +} // namespace Cache diff --git a/README.md b/README.md index 497ce0a..b4a876b 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,141 @@ -# High-Performance Multi-Policy Concurrent Cache System -# 工业级高性能多策略并发缓存系统 (C++17) +# ConcurrentCacheSystem -本项目是一个基于 C++17 实现的高性能、线程安全、低锁内聚的生产级内存缓存库。系统不仅实现了传统缓存淘汰算法,更针对现代多核高并发场景进行了深度的**分片锁优化(Lock Sharding)**,并对经典算法在生产环境中的缺陷(如缓存污染、历史热点退化等)引入了**LRU-K 动态追溯**、**LFU 延迟老化(Lazy Ageing)**以及**ARC 自适应平衡**等大厂级硬核改进。 +[![CI](https://github.com/FILWYZ/ConcurrentCacheSystem/actions/workflows/ci.yml/badge.svg)](https://github.com/FILWYZ/ConcurrentCacheSystem/actions/workflows/ci.yml) +[![C++17](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.cppreference.com/w/cpp/17) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -全库采用 Header-only 架构设计,无第三方依赖,开箱即用,具备极致的编译期优化性能。 +一个无第三方运行时依赖的 C++17 header-only 并发缓存实验项目。它实现了分片 +LRU、LRU-K、带延迟老化的 LFU 与 ARC,并提供统一接口、线程安全访问、原子统计 +和可复现的基准测试。 ---- +> 项目定位:用于学习并验证缓存淘汰策略、锁分片和并发工程实践。当前版本不是 +> 经过生产环境长期验证的通用缓存组件。 -## 🚀 核心架构与技术亮点 +## 特性 -### 1. 高并发分片无锁/低锁设计 (Lock Sharding) -* **痛点**:传统缓存使用全局单锁(如 `std::mutex`),在多线程高并发读写时,锁竞争(Lock Contention)会导致严重的 CPU 上下文切换泥潭,吞吐量断层式下跌。 -* **解耦方案**:系统引入 `ShardedCache` 封装层,通过高性能哈希函数将 Key 路由至不同的独立分片(Shard)。每个分片拥有独占的线程锁,将全局锁竞争稀释至 $1/N$($N$ 为分片数),大幅提升多核并行的物理吞吐极限(Mops/s)。 -* **伪共享防御**:核心单分片类采用 `alignas(64)` 进行**缓存行对齐**,彻底消灭多核多线程在频繁修改锁状态时的**伪共享 (False Sharing)** 硬件级性能陷阱。 +| 策略 | 实现 | 主要用途 | +| --- | --- | --- | +| LRU | `Cache::HighConcurrencyCache` | 通用、访问局部性较强的负载 | +| LRU-K | `Cache::LruKCache` | 降低一次性扫描造成的缓存污染 | +| LFU | `Cache::ShardedLFUCache` | 热点访问频率相对稳定的负载 | +| ARC | `Cache::ShardedARCCache` | 在近期性与访问频率之间自适应 | -### 2. 生产级算法矩阵与硬核改进 -本项目拒绝学院派的简单实现,完全对齐大厂核心基础库的鲁棒性标准: -* **LRU (Least Recently Used)**:标准双向链表 + 哈希表组合,实现严格 $O(1)$ 的查找、提权与淘汰。 -* **LRU-K (抗缓存污染)**:引入历史访问队列与多级晋升机制。通过追溯 Key 的第 $K$ 次访问时间(默认 $K=2$),精准过滤突发性的“全表扫描/冷数据涌入”,彻底解决传统 LRU 的**缓存污染**痛点。 -* **LFU + Lazy Ageing (时效自适应)**:针对 LFU 长期运行下“早期极热数据在后期转冷,但因频次极高无法被淘汰”的**历史残留毒瘤缺陷**,引入了高效的**延迟老化机制**。在单分片内以 $O(1)$ 的时间复杂度平摊衰减历史频次,使其快速适应业务流量波峰的动态切换。 -* **ARC (Adaptive Replacement Cache)**:动态自适应缓存。内部维持 `T1` (近期访问)、`T2` (频繁访问) 两个真实数据队列,以及 `B1`、`B2` 两个**幽灵历史队列 (Ghost List)**。通过反馈控制原理,在运行时无需人工调优即可自适应业务冷热突发波形。 +- 锁分片降低不同 key 之间的锁竞争。 +- `CachePolicy` 提供统一的 `put/get/peek/erase/contains` 接口。 +- 命中、未命中和淘汰计数使用 relaxed atomic,不占用分片锁。 +- CMake、CTest、AddressSanitizer、UndefinedBehaviorSanitizer 和 GitHub Actions。 +- 基准脚本包含热点读、混合读写、顺序扫描和写密集四类负载。 -### 3. 指标向上提权与 Lock-Free 监控 -* 为防止监控打点(吞吐量、命中率、淘汰数)串行访问各分片引发**级联死锁**,系统采用“指标向上提权、全局共享”设计。 -* 外部监控接口 `stats()` 采用 `std::atomic` 和 `std::memory_order_relaxed` 读写,保证监控统计具备 **Lock-Free(无锁)** 的极致非阻塞性能。 +## 快速开始 ---- - -## 🛠️ API 核心使用范例 - -### 1. 基础 LRU 缓存使用 ```cpp #include "LRU.h" -#include + #include +#include + +int main() +{ + Cache::HighConcurrencyCache cache(1024, 8); + cache.put(42, "answer"); -int main() { - // 创建一个容量为 1000 的标准单线程安全 LRU 缓存 - Cache::HighConcurrencyCache cache(1000, 1); - - // 写入 - cache.put(42, "Deep Answer"); - - // 读取 std::string value; if (cache.get(42, value)) { - std::cout << "Hit! Value: " << value << std::endl; + std::cout << value << '\n'; } - return 0; -} \ No newline at end of file + + const Cache::CacheStats stats = cache.stats(); + std::cout << "hit rate: " << stats.hitRate() << '\n'; +} +``` + +直接使用时,将所需头文件和 `CachePolicy.h` 加入 include path 即可。 + +## 构建与测试 + +要求:CMake 3.16+,支持 C++17 的 Clang 或 GCC。 + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +ctest --test-dir build --output-on-failure +``` + +启用 Sanitizer: + +```bash +cmake -S . -B build-sanitized \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCONCURRENT_CACHE_ENABLE_SANITIZERS=ON +cmake --build build-sanitized --parallel +ctest --test-dir build-sanitized --output-on-failure +``` + +## 运行基准 + +```bash +./run_cache_benchmark.sh . 1000000 16384 8 8 +``` + +参数依次为:头文件目录、每个场景的操作数、缓存容量、线程数、分片数。结果会输出 +耗时、吞吐量、命中率、写入数、最终大小和已知淘汰数。 + +也可以使用 Docker: + +```bash +docker compose up --build +``` + +基准结果与 CPU、编译器、线程数、数据分布密切相关。请在固定环境中多次运行,并 +使用中位数比较;不要把单次微基准结果直接等同于生产性能。 + +## 设计概览 + +```text +key + └─ hash(key) % shard_count + └─ shard mutex + ├─ policy metadata + └─ key/value storage + +global stats + └─ relaxed atomics (hits / misses / evictions) +``` + +每个 key 始终路由到固定分片。不同分片可以并行执行,同一分片内由互斥锁保护策略 +元数据。`size()` 等跨分片操作是逐分片快照,不保证与并发写入构成全局线性一致的 +瞬时视图。 + +## 复杂度 + +| 操作 | LRU | LRU-K | LFU | ARC | +| --- | --- | --- | --- | --- | +| `get` | 平均 O(1) | 平均 O(1) | O(log F) | 平均 O(1) | +| `put` | 平均 O(1) | 平均 O(1) | O(log F) | 平均 O(1) | +| 空间 | O(C) | O(C + H) | O(C) | O(C) | + +`C` 是缓存容量,`H` 是 LRU-K 历史区容量,`F` 是当前频率桶数量。 + +## 项目结构 + +```text +. +├── CachePolicy.h +├── LRU.h +├── LFU.h +├── ARC.h +├── tests/cache_tests.cpp +├── run_cache_benchmark.sh +└── .github/workflows/ci.yml +``` + +## 后续计划 + +- 增加 ThreadSanitizer 独立任务和长时间并发压力测试。 +- 增加固定硬件环境下的基准历史与性能回归阈值。 +- 补充自定义 allocator、TTL 和容量动态调整实验。 +- 对比 oneTBB、Folly 等成熟实现,明确适用边界。 + +## License + +[MIT](LICENSE) diff --git a/tests/cache_tests.cpp b/tests/cache_tests.cpp new file mode 100644 index 0000000..c91af33 --- /dev/null +++ b/tests/cache_tests.cpp @@ -0,0 +1,136 @@ +#include "ARC.h" +#include "LFU.h" +#include "LRU.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + throw std::runtime_error( \ + std::string("check failed: ") + #condition); \ + } \ + } while (false) + +void testLruEvictionAndStats() +{ + Cache::HighConcurrencyCache cache(2, 1); + cache.put(1, 10); + cache.put(2, 20); + + int value = 0; + CHECK(cache.get(1, value)); + CHECK(value == 10); + cache.put(3, 30); + + CHECK(!cache.contains(2)); + CHECK(cache.contains(1)); + CHECK(cache.contains(3)); + CHECK(!cache.get(99, value)); + + const auto stats = cache.stats(); + CHECK(stats.hits == 1); + CHECK(stats.misses == 1); + CHECK(stats.evictions == 1); + CHECK(cache.capacity() == 2); + CHECK(cache.size() == 2); +} + +void testExactCapacityAcrossShards() +{ + Cache::HighConcurrencyCache cache(3, 16); + CHECK(cache.shardNum() == 3); + + for (int key = 0; key < 30; ++key) { + cache.put(key, key); + } + CHECK(cache.size() <= cache.capacity()); +} + +void testLruKPromotion() +{ + Cache::LruKCache cache(2, 4, 2, 1); + cache.put(7, 70); + CHECK(cache.historySize() == 1); + cache.put(7, 71); + CHECK(cache.historySize() == 0); + CHECK(cache.mainSize() == 1); + + int value = 0; + CHECK(cache.get(7, value)); + CHECK(value == 71); +} + +template +void testBasicPolicy(CacheType& cache) +{ + cache.put(1, 10); + cache.put(2, 20); + + int value = 0; + CHECK(cache.get(1, value)); + CHECK(value == 10); + CHECK(cache.peek(2, value)); + CHECK(value == 20); + CHECK(cache.erase(2)); + CHECK(!cache.contains(2)); +} + +void testOtherPolicies() +{ + Cache::ShardedLFUCache lfu(4, 2); + Cache::ShardedARCCache arc(4, 2); + testBasicPolicy(lfu); + testBasicPolicy(arc); +} + +void testConcurrentAccess() +{ + constexpr std::size_t threadCount = 8; + constexpr std::size_t keysPerThread = 500; + Cache::HighConcurrencyCache cache( + threadCount * keysPerThread, + static_cast(threadCount)); + + std::vector workers; + workers.reserve(threadCount); + for (std::size_t thread = 0; thread < threadCount; ++thread) { + workers.emplace_back([&, thread]() { + const std::size_t begin = thread * keysPerThread; + const std::size_t end = begin + keysPerThread; + for (std::size_t key = begin; key < end; ++key) { + cache.put(key, key * 2); + } + }); + } + for (auto& worker : workers) { + worker.join(); + } + + CHECK(cache.size() == threadCount * keysPerThread); + for (std::size_t key = 0; key < threadCount * keysPerThread; ++key) { + std::size_t value = 0; + CHECK(cache.get(key, value)); + CHECK(value == key * 2); + } +} + +} // namespace + +int main() +{ + testLruEvictionAndStats(); + testExactCapacityAcrossShards(); + testLruKPromotion(); + testOtherPolicies(); + testConcurrentAccess(); + std::cout << "All cache tests passed\n"; +}