be/src/util/lru_multi_cache.inline.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // |
18 | | // This file is copied from |
19 | | // https://github.com/apache/impala/blob/master/be/src/util/lru-multi-cache.inline.h |
20 | | // and modified by Doris |
21 | | |
22 | | #pragma once |
23 | | |
24 | | #include <glog/logging.h> |
25 | | |
26 | | #include "util/hash_util.hpp" // IWYU pragma: keep |
27 | | #include "util/lru_multi_cache.h" |
28 | | #include "util/time.h" |
29 | | |
30 | | namespace doris { |
31 | | |
32 | | template <typename KeyType, typename ValueType> |
33 | | template <typename... Args> |
34 | | LruMultiCache<KeyType, ValueType>::ValueType_internal::ValueType_internal( |
35 | | LruMultiCache& cache, const KeyType& key, Container_internal& container, Args&&... args) |
36 | 7.31k | : cache(cache), |
37 | 7.31k | key(key), |
38 | 7.31k | container(container), |
39 | 7.31k | value(std::forward<Args>(args)...), |
40 | 7.31k | timestamp_seconds(MonotonicSeconds()) {} |
41 | | |
42 | | template <typename KeyType, typename ValueType> |
43 | 97.0k | bool LruMultiCache<KeyType, ValueType>::ValueType_internal::is_available() { |
44 | 97.0k | return member_hook.is_linked(); |
45 | 97.0k | } |
46 | | |
47 | | template <typename KeyType, typename ValueType> |
48 | | LruMultiCache<KeyType, ValueType>::Accessor::Accessor(ValueType_internal* p_value_internal) |
49 | 110k | : _p_value_internal(p_value_internal) {} |
50 | | |
51 | | template <typename KeyType, typename ValueType> |
52 | 155k | LruMultiCache<KeyType, ValueType>::Accessor::Accessor(Accessor&& rhs) { |
53 | 155k | _p_value_internal = std::move(rhs._p_value_internal); |
54 | 155k | rhs._p_value_internal = nullptr; |
55 | 155k | } |
56 | | template <typename KeyType, typename ValueType> |
57 | 51.7k | auto LruMultiCache<KeyType, ValueType>::Accessor::operator=(Accessor&& rhs) -> Accessor& { |
58 | 51.7k | _p_value_internal = std::move(rhs._p_value_internal); |
59 | 51.7k | rhs._p_value_internal = nullptr; |
60 | 51.7k | return (*this); |
61 | 51.7k | } |
62 | | |
63 | | template <typename KeyType, typename ValueType> |
64 | 265k | LruMultiCache<KeyType, ValueType>::Accessor::~Accessor() { |
65 | 265k | release(); |
66 | 265k | } |
67 | | |
68 | | template <typename KeyType, typename ValueType> |
69 | 420k | ValueType* LruMultiCache<KeyType, ValueType>::Accessor::get() { |
70 | 420k | if (_p_value_internal) { |
71 | 258k | return &(_p_value_internal->value); |
72 | 258k | } |
73 | | |
74 | 162k | return nullptr; |
75 | 420k | } |
76 | | |
77 | | template <typename KeyType, typename ValueType> |
78 | 0 | const KeyType* LruMultiCache<KeyType, ValueType>::Accessor::get_key() const { |
79 | 0 | if (_p_value_internal) { |
80 | 0 | return &(_p_value_internal->key); |
81 | 0 | } |
82 | | |
83 | 0 | return nullptr; |
84 | 0 | } |
85 | | |
86 | | template <typename KeyType, typename ValueType> |
87 | 317k | void LruMultiCache<KeyType, ValueType>::Accessor::release() { |
88 | | /// Nullptr check as it has to be dereferenced to get the cache reference |
89 | | /// No nullptr check is needed inside LruMultiCache::Release() |
90 | 317k | if (_p_value_internal) { |
91 | 51.7k | LruMultiCache& cache = _p_value_internal->cache; |
92 | 51.7k | cache.release(_p_value_internal); |
93 | 51.7k | _p_value_internal = nullptr; |
94 | 51.7k | } |
95 | 317k | } |
96 | | |
97 | | template <typename KeyType, typename ValueType> |
98 | 0 | void LruMultiCache<KeyType, ValueType>::Accessor::destroy() { |
99 | | /// Nullptr check as it has to be dereferenced to get the cache reference |
100 | | /// No nullptr check is needed inside LruMultiCache::destroy() |
101 | 0 | if (_p_value_internal) { |
102 | 0 | LruMultiCache& cache = _p_value_internal->cache; |
103 | 0 | cache.destroy(_p_value_internal); |
104 | 0 | _p_value_internal = nullptr; |
105 | 0 | } |
106 | 0 | } |
107 | | |
108 | | template <typename KeyType, typename ValueType> |
109 | 32 | LruMultiCache<KeyType, ValueType>::LruMultiCache(size_t capacity) : _capacity(capacity), _size(0) {} |
110 | | |
111 | | template <typename KeyType, typename ValueType> |
112 | | size_t LruMultiCache<KeyType, ValueType>::size() { |
113 | | std::lock_guard<std::mutex> g(_lock); |
114 | | return _size; |
115 | | } |
116 | | |
117 | | template <typename KeyType, typename ValueType> |
118 | | size_t LruMultiCache<KeyType, ValueType>::number_of_keys() { |
119 | | std::lock_guard<std::mutex> g(_lock); |
120 | | return _hash_table.size(); |
121 | | } |
122 | | |
123 | | template <typename KeyType, typename ValueType> |
124 | 32 | void LruMultiCache<KeyType, ValueType>::set_capacity(size_t new_capacity) { |
125 | 32 | std::lock_guard<std::mutex> g(_lock); |
126 | 32 | _capacity = new_capacity; |
127 | 32 | } |
128 | | |
129 | | template <typename KeyType, typename ValueType> |
130 | 51.7k | auto LruMultiCache<KeyType, ValueType>::get(const KeyType& key) -> Accessor { |
131 | 51.7k | std::lock_guard<std::mutex> g(_lock); |
132 | 51.7k | auto hash_table_it = _hash_table.find(key); |
133 | | |
134 | | // No owning list found with this key, the caller will have to create a new object |
135 | | // with EmplaceAndGet() |
136 | 51.7k | if (hash_table_it == _hash_table.end()) return Accessor(); |
137 | | |
138 | 45.3k | Container& container = hash_table_it->second; |
139 | | |
140 | | // Empty containers are deleted automatiacally |
141 | 45.3k | DCHECK(!container.empty()); |
142 | | |
143 | | // All the available elements are in the front, only need to check the first |
144 | 45.3k | auto container_it = container.begin(); |
145 | | |
146 | | // No available object found, the caller will have to create a new one with |
147 | | // EmplaceAndGet() |
148 | 45.3k | if (!container_it->is_available()) return Accessor(); |
149 | | |
150 | | // Move the object to the back of the owning list as it is no longer available. |
151 | 44.4k | container.splice(container.end(), container, container_it); |
152 | | |
153 | | // Remove the element from the LRU list as it is no longer available |
154 | 44.4k | container_it->member_hook.unlink(); |
155 | | |
156 | 44.4k | return Accessor(&(*container_it)); |
157 | 45.3k | } |
158 | | |
159 | | template <typename KeyType, typename ValueType> |
160 | | template <typename... Args> |
161 | | auto LruMultiCache<KeyType, ValueType>::emplace_and_get(const KeyType& key, Args&&... args) |
162 | 7.31k | -> Accessor { |
163 | 7.31k | std::lock_guard<std::mutex> g(_lock); |
164 | | |
165 | | // creates default container if there isn't one |
166 | 7.31k | Container& container = _hash_table[key]; |
167 | | |
168 | | // Get the reference of the key stored in unordered_map, the parameter could be |
169 | | // temporary object but std::unordered_map has stable references |
170 | 7.31k | const KeyType& stored_key = _hash_table.find(key)->first; |
171 | | |
172 | | // Place it as the last entry for the owning list, as it just got reserved |
173 | 7.31k | auto container_it = container.emplace(container.end(), (*this), stored_key, container, |
174 | 7.31k | std::forward<Args>(args)...); |
175 | | |
176 | | // Only can set this after emplace |
177 | 7.31k | container_it->it = container_it; |
178 | | |
179 | 7.31k | _size++; |
180 | | |
181 | | // Need to remove the oldest available if the cache is over the capacity |
182 | 7.31k | _evict_one_if_needed(); |
183 | | |
184 | 7.31k | return Accessor(&(*container_it)); |
185 | 7.31k | } |
186 | | |
187 | | template <typename KeyType, typename ValueType> |
188 | 51.7k | void LruMultiCache<KeyType, ValueType>::release(ValueType_internal* p_value_internal) { |
189 | 51.7k | std::lock_guard<std::mutex> g(_lock); |
190 | | |
191 | | // This only can be used by the accessor, which already checks for nullptr |
192 | 51.7k | DCHECK(p_value_internal); |
193 | | |
194 | | // Has to be currently not available |
195 | 51.7k | DCHECK(!p_value_internal->is_available()); |
196 | | |
197 | | // DO NOT update timestamp_seconds when release. |
198 | | // Because we are about to evict cache value after a certain period. |
199 | 51.7k | p_value_internal->timestamp_seconds = MonotonicSeconds(); |
200 | | |
201 | 51.7k | Container& container = p_value_internal->container; |
202 | | |
203 | | // Move the object to the front, keep LRU relation in owning list too to |
204 | | // be able to age out unused objects |
205 | 51.7k | container.splice(container.begin(), container, p_value_internal->it); |
206 | | |
207 | | // Add the object to LRU list too as it is now available for usage |
208 | 51.7k | _lru_list.push_front(container.front()); |
209 | | |
210 | | // In case we overshot the capacity already, the cache can evict the oldest one |
211 | 51.7k | _evict_one_if_needed(); |
212 | 51.7k | } |
213 | | |
214 | | template <typename KeyType, typename ValueType> |
215 | 0 | void LruMultiCache<KeyType, ValueType>::destroy(ValueType_internal* p_value_internal) { |
216 | 0 | std::lock_guard<std::mutex> g(_lock); |
217 | | |
218 | | // This only can be used by the accessor, which already checks for nullptr |
219 | 0 | DCHECK(p_value_internal); |
220 | | |
221 | | // Has to be currently not available |
222 | 0 | DCHECK(!p_value_internal->is_available()); |
223 | |
|
224 | 0 | Container& container = p_value_internal->container; |
225 | |
|
226 | 0 | if (container.size() == 1) { |
227 | | // Last element, owning list can be removed to prevent aging |
228 | 0 | _hash_table.erase(p_value_internal->key); |
229 | 0 | } else { |
230 | | // Remove from owning list |
231 | 0 | container.erase(p_value_internal->it); |
232 | 0 | } |
233 | |
|
234 | 0 | _size--; |
235 | 0 | } |
236 | | |
237 | | template <typename KeyType, typename ValueType> |
238 | | size_t LruMultiCache<KeyType, ValueType>::number_of_available_objects() { |
239 | | std::lock_guard<std::mutex> g(_lock); |
240 | | return _lru_list.size(); |
241 | | } |
242 | | |
243 | | template <typename KeyType, typename ValueType> |
244 | | void LruMultiCache<KeyType, ValueType>::rehash() { |
245 | | std::lock_guard<std::mutex> g(_lock); |
246 | | _hash_table.rehash(_hash_table.bucket_count() + 1); |
247 | | } |
248 | | |
249 | | template <typename KeyType, typename ValueType> |
250 | 0 | void LruMultiCache<KeyType, ValueType>::_evict_one(ValueType_internal& value_internal) { |
251 | | // std::mutex is locked by the caller evicting function |
252 | | // _lock.DCheckLocked(); |
253 | | |
254 | | // Has to be available to evict |
255 | 0 | DCHECK(value_internal.is_available()); |
256 | | |
257 | | // Remove from LRU cache |
258 | 0 | value_internal.member_hook.unlink(); |
259 | |
|
260 | 0 | Container& container = value_internal.container; |
261 | |
|
262 | 0 | if (container.size() == 1) { |
263 | | // Last element, owning list can be removed to prevent aging |
264 | 0 | _hash_table.erase(value_internal.key); |
265 | 0 | } else { |
266 | | // Remove from owning list |
267 | 0 | container.erase(value_internal.it); |
268 | 0 | } |
269 | |
|
270 | 0 | _size--; |
271 | 0 | } |
272 | | |
273 | | template <typename KeyType, typename ValueType> |
274 | 59.0k | void LruMultiCache<KeyType, ValueType>::_evict_one_if_needed() { |
275 | | // std::mutex is locked by the caller public function |
276 | | // _lock.DCheckLocked(); |
277 | | |
278 | 59.0k | if (!_lru_list.empty() && _size > _capacity) { |
279 | 0 | _evict_one(_lru_list.back()); |
280 | 0 | } |
281 | 59.0k | } |
282 | | |
283 | | template <typename KeyType, typename ValueType> |
284 | 78.4k | void LruMultiCache<KeyType, ValueType>::evict_older_than(uint64_t oldest_allowed_timestamp) { |
285 | 78.4k | std::lock_guard<std::mutex> g(_lock); |
286 | | |
287 | | // Stop eviction if |
288 | | // - there are no more available (i.e. evictable) objects |
289 | | // - cache size is below capacity and the oldest object is not older than the limit |
290 | 78.4k | while (!_lru_list.empty() && |
291 | 78.4k | (_size > _capacity || _lru_list.back().timestamp_seconds < oldest_allowed_timestamp)) { |
292 | 0 | _evict_one(_lru_list.back()); |
293 | 0 | } |
294 | 78.4k | } |
295 | | |
296 | | } // namespace doris |