userver: userver/cache/impl/lru.hpp Source File
Loading...
Searching...
No Matches
lru.hpp
1#pragma once
2
3#include <memory>
4#include <vector>
5
6#include <boost/intrusive/link_mode.hpp>
7#include <boost/intrusive/list.hpp>
8#include <boost/intrusive/list_hook.hpp>
9#include <boost/intrusive/unordered_set.hpp>
10#include <boost/intrusive/unordered_set_hook.hpp>
11
12#include <userver/utils/assert.hpp>
13#include <userver/utils/impl/intrusive_link_mode.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace cache::impl {
18
19struct EmptyPlaceholder {};
20
21using LinkMode = utils::impl::IntrusiveLinkMode;
22using LruListHook = boost::intrusive::list_base_hook<LinkMode>;
23using LruHashSetHook = boost::intrusive::unordered_set_base_hook<LinkMode>;
24
25template <class Key, class Value>
26// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
27class LruNode final : public LruListHook, public LruHashSetHook {
28 public:
29 template <typename... Args>
30 explicit LruNode(Key&& key, Args&&... args)
31 : key_(std::move(key)), value_(std::forward<Args>(args)...) {}
32
33 explicit LruNode(Key&& key, Value&& value)
34 : key_(std::move(key)), value_(std::move(value)) {}
35
36 void SetKey(Key key) { key_ = std::move(key); }
37
38 void SetValue(Value&& value) { value_ = std::move(value); }
39
40 const Key& GetKey() const noexcept { return key_; }
41
42 const Value& GetValue() const noexcept { return value_; }
43 Value& GetValue() noexcept { return value_; }
44
45 private:
46 Key key_;
47 Value value_;
48};
49
50template <class Key>
51// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
52class LruNode<Key, EmptyPlaceholder> final : public LruListHook,
53 public LruHashSetHook {
54 public:
55 explicit LruNode(Key&& key, EmptyPlaceholder /*value*/)
56 : key_(std::move(key)) {}
57
58 void SetKey(Key key) { key_ = std::move(key); }
59
60 void SetValue(EmptyPlaceholder /*value*/) noexcept {}
61
62 const Key& GetKey() const noexcept { return key_; }
63
64 EmptyPlaceholder& GetValue() const noexcept {
65 static EmptyPlaceholder kValue{};
66 return kValue;
67 }
68
69 private:
70 Key key_;
71};
72
73template <class Key, class Value>
74const Key& GetKey(const LruNode<Key, Value>& node) noexcept {
75 return node.GetKey();
76}
77
78template <class T>
79const T& GetKey(const T& key) noexcept {
80 return key;
81}
82
83template <typename T, typename U, typename Hash = std::hash<T>,
84 typename Equal = std::equal_to<T>>
85class LruBase final {
86 public:
87 using NodeType = std::unique_ptr<LruNode<T, U>>;
88
89 explicit LruBase(size_t max_size, const Hash& hash, const Equal& equal);
90 ~LruBase() { Clear(); }
91
92 LruBase(LruBase&& other) noexcept
93 : buckets_(std::move(other.buckets_)),
94 map_(std::move(other.map_)),
95 list_(std::move(other.list_)) {
96 other.buckets_.clear();
97 other.map_.clear();
98 other.list_.clear();
99 }
100
101 LruBase& operator=(LruBase&& other) noexcept {
102 if (this != &other) Clear();
103
104 swap(other.buckets_, buckets_);
105 swap(other.map_, map_);
106 swap(other.list_, list_);
107
108 return *this;
109 }
110
111 LruBase(const LruBase& lru) = delete;
112 LruBase& operator=(const LruBase& lru) = delete;
113
114 bool Put(const T& key, U value);
115
116 template <typename... Args>
117 U* Emplace(const T&, Args&&... args);
118
119 void Erase(const T& key);
120
121 U* Get(const T& key);
122
123 const T* GetLeastUsedKey() const;
124
125 U* GetLeastUsedValue();
126
127 NodeType ExtractLeastUsedNode();
128
129 void SetMaxSize(size_t new_max_size);
130
131 void Clear() noexcept;
132
133 template <typename Function>
134 void VisitAll(Function&& func) const;
135
136 template <typename Function>
137 void VisitAll(Function&& func);
138
139 size_t GetSize() const;
140
141 U& InsertNode(NodeType&& node) noexcept;
142 NodeType ExtractNode(const T& key) noexcept;
143
144 std::size_t GetCapacity() const;
145
146 private:
147 using Node = LruNode<T, U>;
148 using List =
149 boost::intrusive::list<Node, boost::intrusive::constant_time_size<false>>;
150
151 struct LruNodeHash : Hash {
152 LruNodeHash(const Hash& h) : Hash{h} {}
153
154 template <class NodeOrKey>
155 auto operator()(const NodeOrKey& x) const {
156 return Hash::operator()(impl::GetKey(x));
157 }
158 };
159
160 struct LruNodeEqual : Equal {
161 LruNodeEqual(const Equal& eq) : Equal{eq} {}
162
163 template <class NodeOrKey1, class NodeOrKey2>
164 auto operator()(const NodeOrKey1& x, const NodeOrKey2& y) const {
165 return Equal::operator()(impl::GetKey(x), impl::GetKey(y));
166 }
167 };
168
169 using Map = boost::intrusive::unordered_set<
170 Node, boost::intrusive::constant_time_size<true>,
171 boost::intrusive::hash<LruNodeHash>,
172 boost::intrusive::equal<LruNodeEqual>>;
173
174 using BucketTraits = typename Map::bucket_traits;
175 using BucketType = typename Map::bucket_type;
176
177 U& Add(const T& key, U value);
178 void MarkRecentlyUsed(Node& node) noexcept;
179 std::unique_ptr<Node> ExtractNode(typename List::iterator it) noexcept;
180
181 std::vector<BucketType> buckets_;
182 Map map_;
183 List list_;
184};
185
186template <typename T, typename U, typename Hash, typename Equal>
187LruBase<T, U, Hash, Equal>::LruBase(size_t max_size, const Hash& hash,
188 const Equal& eq)
189 : buckets_(max_size ? max_size : 1),
190 map_(BucketTraits(buckets_.data(), buckets_.size()), hash, eq) {
191 UASSERT(max_size > 0);
192}
193
194template <typename T, typename U, typename Hash, typename Eq>
195bool LruBase<T, U, Hash, Eq>::Put(const T& key, U value) {
196 auto it = map_.find(key, map_.hash_function(), map_.key_eq());
197 if (it != map_.end()) {
198 it->SetValue(std::move(value));
199 MarkRecentlyUsed(*it);
200 return false;
201 }
202
203 Add(key, std::move(value));
204 return true;
205}
206
207template <typename T, typename U, typename Hash, typename Eq>
208template <typename... Args>
209U* LruBase<T, U, Hash, Eq>::Emplace(const T& key, Args&&... args) {
210 auto* existing = Get(key);
211 if (existing) return existing;
212
213 if constexpr (std::is_move_assignable_v<U>) {
214 return &Add(key, U{std::forward<Args>(args)...});
215 } else {
216 auto node = std::make_unique<Node>(T{key}, std::forward<Args>(args)...);
217 if (map_.size() >= buckets_.size()) {
218 ExtractNode(list_.begin());
219 }
220 return &InsertNode(std::move(node));
221 }
222}
223
224template <typename T, typename U, typename Hash, typename Eq>
225void LruBase<T, U, Hash, Eq>::Erase(const T& key) {
226 auto it = map_.find(key, map_.hash_function(), map_.key_eq());
227 if (it == map_.end()) return;
228 ExtractNode(list_.iterator_to(*it));
229}
230
231template <typename T, typename U, typename Hash, typename Eq>
232U* LruBase<T, U, Hash, Eq>::Get(const T& key) {
233 auto it = map_.find(key, map_.hash_function(), map_.key_eq());
234 if (it == map_.end()) return nullptr;
235 MarkRecentlyUsed(*it);
236 return &it->GetValue();
237}
238
239template <typename T, typename U, typename Hash, typename Eq>
240const T* LruBase<T, U, Hash, Eq>::GetLeastUsedKey() const {
241 if (list_.empty()) return nullptr;
242 return &list_.front().GetKey();
243}
244
245template <typename T, typename U, typename Hash, typename Eq>
246U* LruBase<T, U, Hash, Eq>::GetLeastUsedValue() {
247 if (list_.empty()) return nullptr;
248 return &list_.front().GetValue();
249}
250
251template <typename T, typename U, typename Hash, typename Eq>
252typename LruBase<T, U, Hash, Eq>::NodeType
253LruBase<T, U, Hash, Eq>::ExtractLeastUsedNode() {
254 if (list_.empty()) return std::unique_ptr<LruNode<T, U>>();
255 return ExtractNode(list_.begin());
256}
257
258template <typename T, typename U, typename Hash, typename Eq>
259void LruBase<T, U, Hash, Eq>::SetMaxSize(size_t new_max_size) {
260 UASSERT(new_max_size > 0);
261 if (!new_max_size) ++new_max_size;
262
263 if (buckets_.size() == new_max_size) {
264 return;
265 }
266
267 while (map_.size() > new_max_size) {
268 ExtractNode(list_.begin());
269 }
270
271 std::vector<BucketType> new_buckets(new_max_size);
272 map_.rehash(BucketTraits(new_buckets.data(), new_max_size));
273 buckets_.swap(new_buckets);
274}
275
276template <typename T, typename U, typename Hash, typename Eq>
277void LruBase<T, U, Hash, Eq>::Clear() noexcept {
278 while (!list_.empty()) {
279 ExtractNode(list_.begin());
280 }
281}
282
283template <typename T, typename U, typename Hash, typename Eq>
284template <typename Function>
285void LruBase<T, U, Hash, Eq>::VisitAll(Function&& func) const {
286 for (const auto& node : map_) {
287 func(node.GetKey(), node.GetValue());
288 }
289}
290
291template <typename T, typename U, typename Hash, typename Eq>
292template <typename Function>
293void LruBase<T, U, Hash, Eq>::VisitAll(Function&& func) {
294 for (auto& node : map_) {
295 func(node.GetKey(), node.GetValue());
296 }
297}
298
299template <typename T, typename U, typename Hash, typename Eq>
300size_t LruBase<T, U, Hash, Eq>::GetSize() const {
301 return map_.size();
302}
303
304template <typename T, typename U, typename Hash, typename Eq>
305std::size_t LruBase<T, U, Hash, Eq>::GetCapacity() const {
306 return buckets_.size();
307}
308
309template <typename T, typename U, typename Hash, typename Eq>
310U& LruBase<T, U, Hash, Eq>::Add(const T& key, U value) {
311 if (map_.size() < buckets_.size()) {
312 auto node = std::make_unique<Node>(T{key}, std::move(value));
313 return InsertNode(std::move(node));
314 }
315
316 auto node = ExtractNode(list_.begin());
317 node->SetKey(key);
318 node->SetValue(std::move(value));
319 return InsertNode(std::move(node));
320}
321
322template <typename T, typename U, typename Hash, typename Eq>
323void LruBase<T, U, Hash, Eq>::MarkRecentlyUsed(Node& node) noexcept {
324 list_.splice(list_.end(), list_, list_.iterator_to(node));
325}
326
327template <typename T, typename U, typename Hash, typename Eq>
328std::unique_ptr<LruNode<T, U>> LruBase<T, U, Hash, Eq>::ExtractNode(
329 typename List::iterator it) noexcept {
330 UASSERT(it != list_.end());
331
332 std::unique_ptr<Node> ret(&*it);
333 map_.erase(map_.iterator_to(*it));
334 list_.erase(it);
335 return ret;
336}
337
338template <typename T, typename U, typename Hash, typename Eq>
339U& LruBase<T, U, Hash, Eq>::InsertNode(
340 LruBase<T, U, Hash, Eq>::NodeType&& node) noexcept {
341 UASSERT(node);
342
343 auto [it, ok] = map_.insert(*node); // noexcept
344 UASSERT(ok);
345 list_.insert(list_.end(), *node); // noexcept
346
347 return node.release()->GetValue();
348}
349
350template <typename T, typename U, typename Hash, typename Eq>
351typename LruBase<T, U, Hash, Eq>::NodeType LruBase<T, U, Hash, Eq>::ExtractNode(
352 const T& key) noexcept {
353 auto it = map_.find(key, map_.hash_function(), map_.key_eq());
354 if (it == map_.end()) {
355 return NodeType();
356 }
357
358 return ExtractNode(list_.iterator_to(*it));
359}
360
361} // namespace cache::impl
362
363USERVER_NAMESPACE_END