userver: userver/cache/impl/lru.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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