From fc0bd78a7c1d5578d37d54cf005f2e9ffcbb4e0f Mon Sep 17 00:00:00 2001 From: Vadim Leonov Date: Sat, 17 Jan 2026 18:49:59 +0300 Subject: [PATCH] 2q caching --- .../userver/multi-index-lru/container.hpp | 92 +++++++++++++++---- libraries/multi-index-lru/src/main_test.cpp | 25 +++++ 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp b/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp index 628d853895cc..4f604399bf0f 100644 --- a/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp +++ b/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp @@ -64,21 +64,50 @@ using add_seq_index_t = typename add_seq_index::type; template > class Container { public: - explicit Container(size_t max_size) - : max_size_(max_size) + explicit Container(std::size_t max_size) + : max_size_(max_size), buffer_size_(0) + {} + + // for 2Q-caching, "buffer_size" argument represents size of FIFO buffer + // total cache size is sum of max_size and buffer_size + explicit Container(std::size_t max_size, std::size_t buffer_size) + : max_size_(max_size), buffer_size_(buffer_size) {} template bool emplace(Args&&... args) { - auto& seq_index = container_.template get<0>(); - auto result = seq_index.emplace_front(std::forward(args)...); - - if (!result.second) { - seq_index.relocate(seq_index.begin(), result.first); - } else if (seq_index.size() > max_size_) { - seq_index.pop_back(); + if (buffer_size_ == 0) { + return emplace_to_container(std::forward(args)...); + } else { + auto& seq_index = container_.template get<0>(); + auto& buffer_seq_index = fifo_buffer_.template get<0>(); + + auto args_tuple = std::make_tuple(std::forward(args)...); + + auto result = std::apply([&](auto&&... args) { + return seq_index.emplace_front(std::forward(args)...); + }, args_tuple); + + auto buffer_result = std::apply([&](auto&&... args) { + return buffer_seq_index.emplace_front(std::forward(args)...); + }, args_tuple); + + if (!buffer_result.second && result.second) { + buffer_seq_index.erase(buffer_result.first); + } else if (!result.second) { + buffer_seq_index.erase(buffer_result.first); + seq_index.relocate(seq_index.begin(), result.first); + if (seq_index.size() > max_size_) { + seq_index.pop_back(); + } + } else { + seq_index.erase(result.first); + if (buffer_seq_index.size() > buffer_size_) { + buffer_seq_index.pop_back(); + } + } + return buffer_result.second || result.second; } - return result.second; } bool insert(const Value& value) { return emplace(value); } @@ -88,30 +117,39 @@ class Container { template auto find(const Key& key) { auto& primary_index = container_.template get(); + auto& primary_buffer_index = fifo_buffer_.template get(); auto it = primary_index.find(key); if (it != primary_index.end()) { auto& seq_index = container_.template get<0>(); auto seq_it = container_.template project<0>(it); seq_index.relocate(seq_index.begin(), seq_it); + return it; + } else { + auto buffer_it = primary_buffer_index.find(key); + if (buffer_it != primary_buffer_index.end()) { + this->insert(*buffer_it); + return primary_index.find(key); + } } - return it; + return primary_index.end(); } template bool contains(const Key& key) { - return this->template find(key) != container_.template get().end(); + return (this->template find(key) != container_.template get().end()); } template bool erase(const Key& key) { - return container_.template get().erase(key) > 0; + return (container_.template get().erase(key) > 0) || + (fifo_buffer_.template get().erase(key) > 0); } - std::size_t size() const { return container_.size(); } - bool empty() const { return container_.empty(); } - std::size_t capacity() const { return max_size_; } + std::size_t size() const { return container_.size() + fifo_buffer_.size(); } + bool empty() const { return container_.empty() && fifo_buffer_.empty(); } + std::size_t capacity() const { return max_size_ + buffer_size_; } void set_capacity(std::size_t new_capacity) { max_size_ = new_capacity; @@ -121,7 +159,7 @@ class Container { } } - void clear() { container_.clear(); } + void clear() { container_.clear(); fifo_buffer_.clear(); } template auto end() { @@ -132,9 +170,27 @@ class Container { using ExtendedIndexSpecifierList = impl::add_seq_index_t; using BoostContainer = boost::multi_index::multi_index_container; - + // using BoostList = boost::multi_index::multi_index_container< + // Value, + // boost::multi_index::indexed_by>, + // Allocator>; BoostContainer container_; + BoostContainer fifo_buffer_; std::size_t max_size_; + std::size_t buffer_size_; + + template + bool emplace_to_container(Args&&... args) { + auto& seq_index = container_.template get<0>(); + auto result = seq_index.emplace_front(std::forward(args)...); + + if (!result.second) { + seq_index.relocate(seq_index.begin(), result.first); + } else if (seq_index.size() > max_size_) { + seq_index.pop_back(); + } + return result.second; + } }; } // namespace multi_index_lru diff --git a/libraries/multi-index-lru/src/main_test.cpp b/libraries/multi-index-lru/src/main_test.cpp index 8adc2ca79a5f..abc33d0a4728 100644 --- a/libraries/multi-index-lru/src/main_test.cpp +++ b/libraries/multi-index-lru/src/main_test.cpp @@ -147,6 +147,31 @@ TEST_F(ProductsTest, ProductEviction) { EXPECT_EQ(cache.find("Mouse"), cache.end()); } +TEST_F(ProductsTest, 2QCashingBasic) { + ProductCache cache(2, 2); + // container size = 2 + // buffer size = 2 + + cache.emplace(Product{"A1", "Laptop", 999.99}); + cache.emplace(Product{"A2", "Mouse", 29.99}); + + cache.find("A1"); + cache.find("A2"); + // A1 and A2 move to the container + + for (int i = 3; i < 100; ++i) { + cache.emplace(Product{"A" + std::to_string(i), "product_" + std::to_string(i), static_cast(i)}); + } + + EXPECT_TRUE((cache.contains("A1"))); + EXPECT_TRUE((cache.contains("A2"))); // in the container + + EXPECT_TRUE((cache.contains("A98"))); + EXPECT_TRUE((cache.contains("A99"))); // in the buffer + + EXPECT_FALSE((cache.contains("A3"))); // ousted +} + TEST(Snippet, SimpleUsage) { struct MyValueT { std::string key;