Coverage Report

Created: 2026-04-03 05:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
common/cpp/lru_cache.cpp
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
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
18
// Use of this source code is governed by a BSD-style license that can be
19
// found in the LICENSE file. See the AUTHORS file for names of contributors.
20
21
#include "cpp/lru_cache.h"
22
23
#include <chrono>
24
#include <cstdlib>
25
#include <mutex>
26
#include <new>
27
28
namespace doris {
29
30
namespace {
31
32
21.4M
int64_t unix_millis() {
33
21.4M
    return std::chrono::duration_cast<std::chrono::milliseconds>(
34
21.4M
                   std::chrono::system_clock::now().time_since_epoch())
35
21.4M
            .count();
36
21.4M
}
37
38
} // namespace
39
40
27.5M
uint32_t CacheKey::hash(const char* data, size_t n, uint32_t seed) const {
41
    // Similar to murmur hash
42
27.5M
    const uint32_t m = 0xc6a4a793;
43
27.5M
    const uint32_t r = 24;
44
27.5M
    const char* limit = data + n;
45
27.5M
    uint32_t h = seed ^ (static_cast<uint32_t>(n) * m);
46
47
    // Pick up four bytes at a time
48
564M
    while (data + 4 <= limit) {
49
536M
        uint32_t w = _decode_fixed32(data);
50
536M
        data += 4;
51
536M
        h += w;
52
536M
        h *= m;
53
536M
        h ^= (h >> 16);
54
536M
    }
55
56
    // Pick up remaining bytes
57
27.5M
    switch (limit - data) {
58
2.35M
    case 3:
59
2.35M
        h += static_cast<unsigned char>(data[2]) << 16;
60
61
        // fall through
62
4.09M
    case 2:
63
4.09M
        h += static_cast<unsigned char>(data[1]) << 8;
64
65
        // fall through
66
10.9M
    case 1:
67
10.9M
        h += static_cast<unsigned char>(data[0]);
68
10.9M
        h *= m;
69
10.9M
        h ^= (h >> r);
70
10.9M
        break;
71
72
16.6M
    default:
73
16.6M
        break;
74
27.5M
    }
75
76
27.6M
    return h;
77
27.5M
}
78
79
22.0k
HandleTable::~HandleTable() {
80
22.0k
    delete[] _list;
81
22.0k
}
82
83
// LRU cache implementation
84
24.5M
LRUHandle* HandleTable::lookup(const CacheKey& key, uint32_t hash) {
85
24.5M
    return *_find_pointer(key, hash);
86
24.5M
}
87
88
2.99M
LRUHandle* HandleTable::insert(LRUHandle* h) {
89
2.99M
    LRUHandle** ptr = _find_pointer(h->key(), h->hash);
90
2.99M
    LRUHandle* old = *ptr;
91
2.99M
    h->next_hash = old ? old->next_hash : nullptr;
92
2.99M
    *ptr = h;
93
94
2.99M
    if (old == nullptr) {
95
2.82M
        ++_elems;
96
2.82M
        if (_elems > _length) {
97
            // Since each cache entry is fairly large, we aim for a small
98
            // average linked list length (<= 1).
99
10.1k
            _resize();
100
10.1k
        }
101
2.82M
    }
102
103
2.99M
    return old;
104
2.99M
}
105
106
134k
LRUHandle* HandleTable::remove(const CacheKey& key, uint32_t hash) {
107
134k
    LRUHandle** ptr = _find_pointer(key, hash);
108
134k
    LRUHandle* result = *ptr;
109
110
134k
    if (result != nullptr) {
111
74.8k
        *ptr = result->next_hash;
112
74.8k
        _elems--;
113
74.8k
    }
114
115
134k
    return result;
116
134k
}
117
118
1.77M
bool HandleTable::remove(const LRUHandle* h) {
119
1.77M
    LRUHandle** ptr = &(_list[h->hash & (_length - 1)]);
120
1.92M
    while (*ptr != nullptr && *ptr != h) {
121
153k
        ptr = &(*ptr)->next_hash;
122
153k
    }
123
124
1.77M
    LRUHandle* result = *ptr;
125
1.77M
    if (result != nullptr) {
126
1.77M
        *ptr = result->next_hash;
127
1.77M
        _elems--;
128
1.77M
        return true;
129
1.77M
    }
130
18.4E
    return false;
131
1.77M
}
132
133
27.6M
LRUHandle** HandleTable::_find_pointer(const CacheKey& key, uint32_t hash) {
134
27.6M
    LRUHandle** ptr = &(_list[hash & (_length - 1)]);
135
43.9M
    while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
136
16.2M
        ptr = &(*ptr)->next_hash;
137
16.2M
    }
138
139
27.6M
    return ptr;
140
27.6M
}
141
142
40.6k
void HandleTable::_resize() {
143
40.6k
    uint32_t new_length = 16;
144
66.6k
    while (new_length < _elems * 1.5) {
145
26.0k
        new_length *= 2;
146
26.0k
    }
147
148
40.6k
    auto** new_list = new (std::nothrow) LRUHandle*[new_length];
149
40.6k
    memset(new_list, 0, sizeof(new_list[0]) * new_length);
150
151
40.6k
    uint32_t count = 0;
152
1.92M
    for (uint32_t i = 0; i < _length; i++) {
153
1.88M
        LRUHandle* h = _list[i];
154
3.78M
        while (h != nullptr) {
155
1.89M
            LRUHandle* next = h->next_hash;
156
1.89M
            uint32_t hash = h->hash;
157
1.89M
            LRUHandle** ptr = &new_list[hash & (new_length - 1)];
158
1.89M
            h->next_hash = *ptr;
159
1.89M
            *ptr = h;
160
1.89M
            h = next;
161
1.89M
            count++;
162
1.89M
        }
163
1.88M
    }
164
165
40.6k
    DCHECK_EQ(_elems, count);
166
40.6k
    delete[] _list;
167
40.6k
    _list = new_list;
168
40.6k
    _length = new_length;
169
40.6k
}
170
171
1.62M
uint32_t HandleTable::element_count() const {
172
1.62M
    return _elems;
173
1.62M
}
174
175
30.4k
LRUCache::LRUCache(LRUCacheType type, bool is_lru_k) : _type(type), _is_lru_k(is_lru_k) {
176
    // Make empty circular linked list
177
30.4k
    _lru_normal.next = &_lru_normal;
178
30.4k
    _lru_normal.prev = &_lru_normal;
179
30.4k
    _lru_durable.next = &_lru_durable;
180
30.4k
    _lru_durable.prev = &_lru_durable;
181
30.4k
}
182
183
22.0k
LRUCache::~LRUCache() {
184
22.0k
    prune();
185
22.0k
}
186
187
30.4k
PrunedInfo LRUCache::set_capacity(size_t capacity) {
188
30.4k
    LRUHandle* last_ref_list = nullptr;
189
30.4k
    {
190
30.4k
        std::lock_guard l(_mutex);
191
30.4k
        if (capacity > _capacity) {
192
30.4k
            _capacity = capacity;
193
30.4k
            return {0, 0};
194
30.4k
        }
195
8
        _capacity = capacity;
196
8
        _evict_from_lru(0, &last_ref_list);
197
8
    }
198
199
0
    int64_t pruned_count = 0;
200
8
    int64_t pruned_size = 0;
201
56.0k
    while (last_ref_list != nullptr) {
202
55.9k
        ++pruned_count;
203
55.9k
        pruned_size += last_ref_list->total_size;
204
55.9k
        LRUHandle* next = last_ref_list->next;
205
55.9k
        last_ref_list->free();
206
55.9k
        last_ref_list = next;
207
55.9k
    }
208
8
    return {pruned_count, pruned_size};
209
30.4k
}
210
211
1.60M
uint64_t LRUCache::get_lookup_count() {
212
1.60M
    std::lock_guard l(_mutex);
213
1.60M
    return _lookup_count;
214
1.60M
}
215
216
1.60M
uint64_t LRUCache::get_hit_count() {
217
1.60M
    std::lock_guard l(_mutex);
218
1.60M
    return _hit_count;
219
1.60M
}
220
221
1.60M
uint64_t LRUCache::get_stampede_count() {
222
1.60M
    std::lock_guard l(_mutex);
223
1.60M
    return _stampede_count;
224
1.60M
}
225
226
1.60M
uint64_t LRUCache::get_miss_count() {
227
1.60M
    std::lock_guard l(_mutex);
228
1.60M
    return _miss_count;
229
1.60M
}
230
231
1.68M
size_t LRUCache::get_usage() {
232
1.68M
    std::lock_guard l(_mutex);
233
1.68M
    return _usage;
234
1.68M
}
235
236
1.60M
size_t LRUCache::get_capacity() {
237
1.60M
    std::lock_guard l(_mutex);
238
1.60M
    return _capacity;
239
1.60M
}
240
241
1.60M
size_t LRUCache::get_element_count() {
242
1.60M
    std::lock_guard l(_mutex);
243
1.60M
    return _table.element_count();
244
1.60M
}
245
246
22.7M
bool LRUCache::_unref(LRUHandle* e) {
247
22.7M
    DCHECK(e->refs > 0);
248
22.7M
    e->refs--;
249
22.7M
    return e->refs == 0;
250
22.7M
}
251
252
16.5M
void LRUCache::_lru_remove(LRUHandle* e) {
253
16.5M
    e->next->prev = e->prev;
254
16.5M
    e->prev->next = e->next;
255
16.5M
    e->prev = e->next = nullptr;
256
257
16.5M
    if (_cache_value_check_timestamp) {
258
8.46k
        if (e->priority == CachePriority::NORMAL) {
259
8.46k
            auto pair = std::make_pair(_cache_value_time_extractor(e->value), e);
260
8.46k
            auto found_it = _sorted_normal_entries_with_timestamp.find(pair);
261
8.48k
            if (found_it != _sorted_normal_entries_with_timestamp.end()) {
262
8.48k
                _sorted_normal_entries_with_timestamp.erase(found_it);
263
8.48k
            }
264
8.46k
        } else if (e->priority == CachePriority::DURABLE) {
265
0
            auto pair = std::make_pair(_cache_value_time_extractor(e->value), e);
266
0
            auto found_it = _sorted_durable_entries_with_timestamp.find(pair);
267
0
            if (found_it != _sorted_durable_entries_with_timestamp.end()) {
268
0
                _sorted_durable_entries_with_timestamp.erase(found_it);
269
0
            }
270
0
        }
271
8.46k
    }
272
16.5M
}
273
274
17.5M
void LRUCache::_lru_append(LRUHandle* list, LRUHandle* e) {
275
    // Make "e" newest entry by inserting just before *list
276
17.5M
    e->next = list;
277
17.5M
    e->prev = list->prev;
278
17.5M
    e->prev->next = e;
279
17.5M
    e->next->prev = e;
280
281
    // _cache_value_check_timestamp is true,
282
    // means evict entry will depends on the timestamp asc set,
283
    // the timestamp is updated by higher level caller,
284
    // and the timestamp of hit entry is different with the insert entry,
285
    // that is why need check timestamp to evict entry,
286
    // in order to keep the survival time of hit entries
287
    // longer than the entries just inserted,
288
    // so use asc set to sorted these entries's timestamp and LRUHandle*
289
17.5M
    if (_cache_value_check_timestamp) {
290
17.1k
        if (e->priority == CachePriority::NORMAL) {
291
17.1k
            _sorted_normal_entries_with_timestamp.insert(
292
17.1k
                    std::make_pair(_cache_value_time_extractor(e->value), e));
293
17.1k
        } else if (e->priority == CachePriority::DURABLE) {
294
0
            _sorted_durable_entries_with_timestamp.insert(
295
0
                    std::make_pair(_cache_value_time_extractor(e->value), e));
296
0
        }
297
17.1k
    }
298
17.5M
}
299
300
24.3M
Cache::Handle* LRUCache::lookup(const CacheKey& key, uint32_t hash) {
301
24.3M
    std::lock_guard l(_mutex);
302
24.3M
    ++_lookup_count;
303
24.3M
    LRUHandle* e = _table.lookup(key, hash);
304
24.3M
    if (e != nullptr) {
305
        // we get it from _table, so in_cache must be true
306
18.4M
        DCHECK(e->in_cache);
307
18.4M
        if (e->refs == 1) {
308
            // only in LRU free list, remove it from list
309
14.6M
            _lru_remove(e);
310
14.6M
        }
311
18.4M
        e->refs++;
312
18.4M
        ++_hit_count;
313
18.4M
        e->last_visit_time = unix_millis();
314
18.4M
    } else {
315
5.83M
        ++_miss_count;
316
5.83M
    }
317
318
    // If key not exist in cache, and is lru k cache, and key in visits list,
319
    // then move the key to beginning of the visits list.
320
    // key in visits list indicates that the key has been inserted once after the cache is full.
321
24.3M
    if (e == nullptr && _is_lru_k) {
322
2.64M
        auto it = _visits_lru_cache_map.find(hash);
323
2.64M
        if (it != _visits_lru_cache_map.end()) {
324
4.22k
            _visits_lru_cache_list.splice(_visits_lru_cache_list.begin(), _visits_lru_cache_list,
325
4.22k
                                          it->second);
326
4.22k
        }
327
2.64M
    }
328
24.3M
    return reinterpret_cast<Cache::Handle*>(e);
329
24.3M
}
330
331
20.6M
void LRUCache::release(Cache::Handle* handle) {
332
20.6M
    if (handle == nullptr) {
333
0
        return;
334
0
    }
335
20.6M
    auto* e = reinterpret_cast<LRUHandle*>(handle);
336
20.6M
    bool last_ref = false;
337
20.6M
    {
338
20.6M
        std::lock_guard l(_mutex);
339
        // if last_ref is true, key may have been evict from the cache,
340
        // or if it is lru k, first insert of key may have failed.
341
20.6M
        last_ref = _unref(e);
342
20.6M
        if (e->in_cache && e->refs == 1) {
343
            // only exists in cache
344
17.5M
            if (_usage > _capacity) {
345
                // take this opportunity and remove the item
346
26.8k
                bool removed = _table.remove(e);
347
26.8k
                DCHECK(removed);
348
26.8k
                e->in_cache = false;
349
26.8k
                _unref(e);
350
                // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
351
                // see the comment for old entry in `LRUCache::insert`.
352
26.8k
                _usage -= e->total_size;
353
26.8k
                last_ref = true;
354
17.5M
            } else {
355
                // put it to LRU free list
356
17.5M
                if (e->priority == CachePriority::NORMAL) {
357
17.5M
                    _lru_append(&_lru_normal, e);
358
18.4E
                } else if (e->priority == CachePriority::DURABLE) {
359
18
                    _lru_append(&_lru_durable, e);
360
18
                }
361
17.5M
            }
362
17.5M
        }
363
20.6M
    }
364
365
    // free handle out of mutex
366
20.6M
    if (last_ref) {
367
73.4k
        e->free();
368
73.4k
    }
369
20.6M
}
370
371
9.84k
void LRUCache::_evict_from_lru_with_time(size_t total_size, LRUHandle** to_remove_head) {
372
    // 1. evict normal cache entries
373
9.87k
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
374
9.87k
           !_sorted_normal_entries_with_timestamp.empty()) {
375
23
        auto entry_pair = _sorted_normal_entries_with_timestamp.begin();
376
23
        LRUHandle* remove_handle = entry_pair->second;
377
23
        DCHECK(remove_handle != nullptr);
378
23
        DCHECK(remove_handle->priority == CachePriority::NORMAL);
379
23
        _evict_one_entry(remove_handle);
380
23
        remove_handle->next = *to_remove_head;
381
23
        *to_remove_head = remove_handle;
382
23
    }
383
384
    // 2. evict durable cache entries if need
385
9.85k
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
386
9.84k
           !_sorted_durable_entries_with_timestamp.empty()) {
387
0
        auto entry_pair = _sorted_durable_entries_with_timestamp.begin();
388
0
        LRUHandle* remove_handle = entry_pair->second;
389
0
        DCHECK(remove_handle != nullptr);
390
0
        DCHECK(remove_handle->priority == CachePriority::DURABLE);
391
0
        _evict_one_entry(remove_handle);
392
0
        remove_handle->next = *to_remove_head;
393
0
        *to_remove_head = remove_handle;
394
0
    }
395
9.84k
}
396
397
2.98M
void LRUCache::_evict_from_lru(size_t total_size, LRUHandle** to_remove_head) {
398
    // 1. evict normal cache entries
399
3.41M
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
400
3.41M
           _lru_normal.next != &_lru_normal) {
401
438k
        LRUHandle* old = _lru_normal.next;
402
438k
        DCHECK(old->priority == CachePriority::NORMAL);
403
438k
        _evict_one_entry(old);
404
438k
        old->next = *to_remove_head;
405
438k
        *to_remove_head = old;
406
438k
    }
407
    // 2. evict durable cache entries if need
408
2.98M
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
409
2.98M
           _lru_durable.next != &_lru_durable) {
410
4
        LRUHandle* old = _lru_durable.next;
411
4
        DCHECK(old->priority == CachePriority::DURABLE);
412
4
        _evict_one_entry(old);
413
4
        old->next = *to_remove_head;
414
4
        *to_remove_head = old;
415
4
    }
416
2.98M
}
417
418
1.74M
void LRUCache::_evict_one_entry(LRUHandle* e) {
419
1.74M
    DCHECK(e->in_cache);
420
1.74M
    DCHECK(e->refs == 1); // LRU list contains elements which may be evicted
421
1.74M
    _lru_remove(e);
422
1.74M
    bool removed = _table.remove(e);
423
1.74M
    DCHECK(removed);
424
1.74M
    e->in_cache = false;
425
1.74M
    _unref(e);
426
    // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
427
    // see the comment for old entry in `LRUCache::insert`.
428
1.74M
    _usage -= e->total_size;
429
1.74M
}
430
431
7.77M
bool LRUCache::_check_element_count_limit() {
432
7.77M
    return _element_count_capacity != 0 && _table.element_count() >= _element_count_capacity;
433
7.77M
}
434
435
// After cache is full,
436
// 1.Return false. If key has been inserted into the visits list before,
437
// key is allowed to be inserted into cache this time (this will trigger cache evict),
438
// and key is removed from the visits list.
439
// 2. Return true. If key not in visits list, insert it into visits list.
440
1.88M
bool LRUCache::_lru_k_insert_visits_list(size_t total_size, visits_lru_cache_key visits_key) {
441
1.88M
    if (_usage + total_size > _capacity ||
442
1.88M
        _check_element_count_limit()) { // this line no lock required
443
15.3k
        auto it = _visits_lru_cache_map.find(visits_key);
444
15.3k
        if (it != _visits_lru_cache_map.end()) {
445
4.25k
            _visits_lru_cache_usage -= it->second->second;
446
4.25k
            _visits_lru_cache_list.erase(it->second);
447
4.25k
            _visits_lru_cache_map.erase(it);
448
11.0k
        } else {
449
            // _visits_lru_cache_list capacity is same as the cache itself.
450
            // If _visits_lru_cache_list is full, some keys will also be evict.
451
11.8k
            while (_visits_lru_cache_usage + total_size > _capacity &&
452
11.8k
                   _visits_lru_cache_usage != 0) {
453
755
                DCHECK(!_visits_lru_cache_map.empty());
454
755
                _visits_lru_cache_usage -= _visits_lru_cache_list.back().second;
455
755
                _visits_lru_cache_map.erase(_visits_lru_cache_list.back().first);
456
755
                _visits_lru_cache_list.pop_back();
457
755
            }
458
            // 1. If true, insert key at the beginning of _visits_lru_cache_list.
459
            // 2. If false, it means total_size > cache _capacity, preventing this insert.
460
11.0k
            if (_visits_lru_cache_usage + total_size <= _capacity) {
461
6.90k
                _visits_lru_cache_list.emplace_front(visits_key, total_size);
462
6.90k
                _visits_lru_cache_map[visits_key] = _visits_lru_cache_list.begin();
463
6.90k
                _visits_lru_cache_usage += total_size;
464
6.90k
            }
465
11.0k
            return true;
466
11.0k
        }
467
15.3k
    }
468
1.87M
    return false;
469
1.88M
}
470
471
Cache::Handle* LRUCache::insert(const CacheKey& key, uint32_t hash, void* value, size_t charge,
472
2.99M
                                CachePriority priority, CacheValueDeleter deleter) {
473
2.99M
    size_t handle_size = sizeof(LRUHandle) - 1 + key.size();
474
2.99M
    auto* e = reinterpret_cast<LRUHandle*>(malloc(handle_size));
475
2.99M
    e->value = value;
476
2.99M
    e->charge = charge;
477
2.99M
    e->key_length = key.size();
478
    // if LRUCacheType::NUMBER, charge not add handle_size,
479
    // because charge at this time is no longer the memory size, but an weight.
480
2.99M
    e->total_size = (_type == LRUCacheType::SIZE ? handle_size + charge : charge);
481
2.99M
    e->hash = hash;
482
2.99M
    e->refs = 1; // only one for the returned handle.
483
2.99M
    e->next = e->prev = nullptr;
484
2.99M
    e->in_cache = false;
485
2.99M
    e->priority = priority;
486
2.99M
    e->type = _type;
487
2.99M
    e->deleter = deleter;
488
2.99M
    memcpy(e->key_data, key.data(), key.size());
489
2.99M
    e->last_visit_time = unix_millis();
490
491
2.99M
    LRUHandle* to_remove_head = nullptr;
492
2.99M
    {
493
2.99M
        std::lock_guard l(_mutex);
494
495
2.99M
        if (_is_lru_k && _lru_k_insert_visits_list(e->total_size, hash)) {
496
11.0k
            return reinterpret_cast<Cache::Handle*>(e);
497
11.0k
        }
498
499
        // Free the space following strict LRU policy until enough space
500
        // is freed or the lru list is empty
501
2.98M
        if (_cache_value_check_timestamp) {
502
9.86k
            _evict_from_lru_with_time(e->total_size, &to_remove_head);
503
2.97M
        } else {
504
2.97M
            _evict_from_lru(e->total_size, &to_remove_head);
505
2.97M
        }
506
507
        // insert into the cache
508
        // note that the cache might get larger than its capacity if not enough
509
        // space was freed
510
2.98M
        auto* old = _table.insert(e);
511
2.98M
        e->in_cache = true;
512
2.98M
        _usage += e->total_size;
513
2.98M
        e->refs++; // one for the returned handle, one for LRUCache.
514
2.98M
        if (old != nullptr) {
515
173k
            _stampede_count++;
516
173k
            old->in_cache = false;
517
            // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
518
            // Whether the reference of the old entry is 0, the cache usage is subtracted here,
519
            // because the old entry has been removed from the cache and should not be counted in the cache capacity,
520
            // but the memory of the old entry is still tracked by the cache memory_tracker.
521
            // After all the old handles are released, the old entry will be freed and the memory of the old entry
522
            // will be released from the cache memory_tracker.
523
173k
            _usage -= old->total_size;
524
            // if false, old entry is being used externally, just ref-- and sub _usage,
525
173k
            if (_unref(old)) {
526
                // old is on LRU because it's in cache and its reference count
527
                // was just 1 (Unref returned 0)
528
129k
                _lru_remove(old);
529
129k
                old->next = to_remove_head;
530
129k
                to_remove_head = old;
531
129k
            }
532
173k
        }
533
2.98M
    }
534
535
    // we free the entries here outside of mutex for
536
    // performance reasons
537
3.50M
    while (to_remove_head != nullptr) {
538
512k
        LRUHandle* next = to_remove_head->next;
539
512k
        to_remove_head->free();
540
512k
        to_remove_head = next;
541
512k
    }
542
543
2.98M
    return reinterpret_cast<Cache::Handle*>(e);
544
2.99M
}
545
546
134k
void LRUCache::erase(const CacheKey& key, uint32_t hash) {
547
134k
    LRUHandle* e = nullptr;
548
134k
    bool last_ref = false;
549
134k
    {
550
134k
        std::lock_guard l(_mutex);
551
134k
        e = _table.remove(key, hash);
552
134k
        if (e != nullptr) {
553
74.8k
            last_ref = _unref(e);
554
            // if last_ref is false or in_cache is false, e must not be in lru
555
74.8k
            if (last_ref && e->in_cache) {
556
                // locate in free list
557
74.8k
                _lru_remove(e);
558
74.8k
            }
559
74.8k
            e->in_cache = false;
560
            // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
561
            // see the comment for old entry in `LRUCache::insert`.
562
74.8k
            _usage -= e->total_size;
563
74.8k
        }
564
134k
    }
565
    // free handle out of mutex, when last_ref is true, e must not be nullptr
566
134k
    if (last_ref) {
567
74.8k
        e->free();
568
74.8k
    }
569
134k
}
570
571
22.0k
PrunedInfo LRUCache::prune() {
572
22.0k
    LRUHandle* to_remove_head = nullptr;
573
22.0k
    {
574
22.0k
        std::lock_guard l(_mutex);
575
358k
        while (_lru_normal.next != &_lru_normal) {
576
336k
            LRUHandle* old = _lru_normal.next;
577
336k
            _evict_one_entry(old);
578
336k
            old->next = to_remove_head;
579
336k
            to_remove_head = old;
580
336k
        }
581
22.0k
        while (_lru_durable.next != &_lru_durable) {
582
6
            LRUHandle* old = _lru_durable.next;
583
6
            _evict_one_entry(old);
584
6
            old->next = to_remove_head;
585
6
            to_remove_head = old;
586
6
        }
587
22.0k
    }
588
22.0k
    int64_t pruned_count = 0;
589
22.0k
    int64_t pruned_size = 0;
590
358k
    while (to_remove_head != nullptr) {
591
336k
        ++pruned_count;
592
336k
        pruned_size += to_remove_head->total_size;
593
336k
        LRUHandle* next = to_remove_head->next;
594
336k
        to_remove_head->free();
595
336k
        to_remove_head = next;
596
336k
    }
597
22.0k
    return {pruned_count, pruned_size};
598
22.0k
}
599
600
23.0k
PrunedInfo LRUCache::prune_if(CachePrunePredicate pred, bool lazy_mode) {
601
23.0k
    LRUHandle* to_remove_head = nullptr;
602
23.0k
    {
603
23.0k
        std::lock_guard l(_mutex);
604
23.0k
        LRUHandle* p = _lru_normal.next;
605
995k
        while (p != &_lru_normal) {
606
990k
            LRUHandle* next = p->next;
607
990k
            if (pred(p)) {
608
972k
                _evict_one_entry(p);
609
972k
                p->next = to_remove_head;
610
972k
                to_remove_head = p;
611
972k
            } else if (lazy_mode) {
612
18.2k
                break;
613
18.2k
            }
614
972k
            p = next;
615
972k
        }
616
617
23.0k
        p = _lru_durable.next;
618
23.0k
        while (p != &_lru_durable) {
619
10
            LRUHandle* next = p->next;
620
10
            if (pred(p)) {
621
2
                _evict_one_entry(p);
622
2
                p->next = to_remove_head;
623
2
                to_remove_head = p;
624
8
            } else if (lazy_mode) {
625
3
                break;
626
3
            }
627
7
            p = next;
628
7
        }
629
23.0k
    }
630
23.0k
    int64_t pruned_count = 0;
631
23.0k
    int64_t pruned_size = 0;
632
995k
    while (to_remove_head != nullptr) {
633
972k
        ++pruned_count;
634
972k
        pruned_size += to_remove_head->total_size;
635
972k
        LRUHandle* next = to_remove_head->next;
636
972k
        to_remove_head->free();
637
972k
        to_remove_head = next;
638
972k
    }
639
23.0k
    return {pruned_count, pruned_size};
640
23.0k
}
641
642
512
void LRUCache::for_each_entry(const std::function<void(const LRUHandle*)>& visitor) {
643
512
    std::lock_guard l(_mutex);
644
45.0k
    for (LRUHandle* p = _lru_normal.next; p != &_lru_normal; p = p->next) {
645
44.5k
        visitor(p);
646
44.5k
    }
647
512
    for (LRUHandle* p = _lru_durable.next; p != &_lru_durable; p = p->next) {
648
0
        visitor(p);
649
0
    }
650
512
}
651
652
1.90k
void LRUCache::set_cache_value_time_extractor(CacheValueTimeExtractor cache_value_time_extractor) {
653
1.90k
    _cache_value_time_extractor = cache_value_time_extractor;
654
1.90k
}
655
656
1.90k
void LRUCache::set_cache_value_check_timestamp(bool cache_value_check_timestamp) {
657
1.90k
    _cache_value_check_timestamp = cache_value_check_timestamp;
658
1.90k
}
659
660
27.5M
inline uint32_t ShardedLRUCache::_hash_slice(const CacheKey& s) {
661
27.5M
    return s.hash(s.data(), s.size(), 0);
662
27.5M
}
663
664
ShardedLRUCache::ShardedLRUCache(const std::string& name, size_t capacity, LRUCacheType type,
665
                                 uint32_t num_shards, uint32_t total_element_count_capacity,
666
                                 bool is_lru_k)
667
1.24k
        : _name(name),
668
1.24k
          _num_shard_bits(__builtin_ctz(num_shards)),
669
1.24k
          _num_shards(num_shards),
670
1.24k
          _last_id(1),
671
1.24k
          _capacity(capacity) {
672
1.24k
    CHECK(num_shards > 0) << "num_shards cannot be 0";
673
1.24k
    CHECK_EQ((num_shards & (num_shards - 1)), 0)
674
0
            << "num_shards should be power of two, but got " << num_shards;
675
676
1.24k
    const size_t per_shard = (capacity + (_num_shards - 1)) / _num_shards;
677
1.24k
    const uint32_t per_shard_element_count_capacity =
678
1.24k
            (total_element_count_capacity + (_num_shards - 1)) / _num_shards;
679
1.24k
    auto** shards = new (std::nothrow) LRUCache*[_num_shards];
680
31.7k
    for (int s = 0; s < _num_shards; s++) {
681
30.4k
        shards[s] = new LRUCache(type, is_lru_k);
682
30.4k
        shards[s]->set_capacity(per_shard);
683
30.4k
        shards[s]->set_element_count_capacity(per_shard_element_count_capacity);
684
30.4k
    }
685
1.24k
    _shards = shards;
686
1.24k
}
687
688
ShardedLRUCache::ShardedLRUCache(const std::string& name, size_t capacity, LRUCacheType type,
689
                                 uint32_t num_shards,
690
                                 CacheValueTimeExtractor cache_value_time_extractor,
691
                                 bool cache_value_check_timestamp,
692
                                 uint32_t total_element_count_capacity, bool is_lru_k)
693
124
        : ShardedLRUCache(name, capacity, type, num_shards, total_element_count_capacity,
694
124
                          is_lru_k) {
695
2.03k
    for (int s = 0; s < _num_shards; s++) {
696
1.90k
        _shards[s]->set_cache_value_time_extractor(cache_value_time_extractor);
697
1.90k
        _shards[s]->set_cache_value_check_timestamp(cache_value_check_timestamp);
698
1.90k
    }
699
124
}
700
701
1.16k
ShardedLRUCache::~ShardedLRUCache() {
702
1.16k
    _metrics_recorder.reset();
703
1.16k
    if (_shards) {
704
23.2k
        for (int s = 0; s < _num_shards; s++) {
705
22.0k
            delete _shards[s];
706
22.0k
        }
707
1.16k
        delete[] _shards;
708
1.16k
    }
709
1.16k
}
710
711
14
PrunedInfo ShardedLRUCache::set_capacity(size_t capacity) {
712
14
    std::lock_guard l(_mutex);
713
14
    PrunedInfo pruned_info;
714
14
    const size_t per_shard = (capacity + (_num_shards - 1)) / _num_shards;
715
28
    for (int s = 0; s < _num_shards; s++) {
716
14
        PrunedInfo info = _shards[s]->set_capacity(per_shard);
717
14
        pruned_info.pruned_count += info.pruned_count;
718
14
        pruned_info.pruned_size += info.pruned_size;
719
14
    }
720
14
    _capacity = capacity;
721
14
    return pruned_info;
722
14
}
723
724
16.0k
size_t ShardedLRUCache::get_capacity() {
725
16.0k
    std::lock_guard l(_mutex);
726
16.0k
    return _capacity;
727
16.0k
}
728
729
Cache::Handle* ShardedLRUCache::insert(const CacheKey& key, void* value, size_t charge,
730
3.00M
                                       CachePriority priority, CacheValueDeleter deleter) {
731
3.00M
    const uint32_t hash = _hash_slice(key);
732
3.00M
    return _shards[_shard(hash)]->insert(key, hash, value, charge, priority, deleter);
733
3.00M
}
734
735
24.4M
Cache::Handle* ShardedLRUCache::lookup(const CacheKey& key) {
736
24.4M
    const uint32_t hash = _hash_slice(key);
737
24.4M
    return _shards[_shard(hash)]->lookup(key, hash);
738
24.4M
}
739
740
20.7M
void ShardedLRUCache::release(Handle* handle) {
741
20.7M
    auto* h = reinterpret_cast<LRUHandle*>(handle);
742
20.7M
    _shards[_shard(h->hash)]->release(handle);
743
20.7M
}
744
745
134k
void ShardedLRUCache::erase(const CacheKey& key) {
746
134k
    const uint32_t hash = _hash_slice(key);
747
134k
    _shards[_shard(hash)]->erase(key, hash);
748
134k
}
749
750
19.1M
void* ShardedLRUCache::value(Handle* handle) {
751
19.1M
    return reinterpret_cast<LRUHandle*>(handle)->value;
752
19.1M
}
753
754
2
uint64_t ShardedLRUCache::new_id() {
755
2
    return _last_id.fetch_add(1, std::memory_order_relaxed);
756
2
}
757
758
0
PrunedInfo ShardedLRUCache::prune() {
759
0
    PrunedInfo pruned_info;
760
0
    for (int s = 0; s < _num_shards; s++) {
761
0
        PrunedInfo info = _shards[s]->prune();
762
0
        pruned_info.pruned_count += info.pruned_count;
763
0
        pruned_info.pruned_size += info.pruned_size;
764
0
    }
765
0
    return pruned_info;
766
0
}
767
768
449
PrunedInfo ShardedLRUCache::prune_if(CachePrunePredicate pred, bool lazy_mode) {
769
449
    PrunedInfo pruned_info;
770
23.5k
    for (int s = 0; s < _num_shards; s++) {
771
23.0k
        PrunedInfo info = _shards[s]->prune_if(pred, lazy_mode);
772
23.0k
        pruned_info.pruned_count += info.pruned_count;
773
23.0k
        pruned_info.pruned_size += info.pruned_size;
774
23.0k
    }
775
449
    return pruned_info;
776
449
}
777
778
2
void ShardedLRUCache::for_each_entry(const std::function<void(const LRUHandle*)>& visitor) {
779
514
    for (int s = 0; s < _num_shards; s++) {
780
512
        _shards[s]->for_each_entry(visitor);
781
512
    }
782
2
}
783
784
18.1k
int64_t ShardedLRUCache::get_usage() {
785
18.1k
    size_t total_usage = 0;
786
102k
    for (int i = 0; i < _num_shards; i++) {
787
83.8k
        total_usage += _shards[i]->get_usage();
788
83.8k
    }
789
18.1k
    return total_usage;
790
18.1k
}
791
792
10
size_t ShardedLRUCache::get_element_count() {
793
10
    size_t total_element_count = 0;
794
202
    for (int i = 0; i < _num_shards; i++) {
795
192
        total_element_count += _shards[i]->get_element_count();
796
192
    }
797
10
    return total_element_count;
798
10
}
799
800
15.2k
ShardedLRUCache::MetricsSnapshot ShardedLRUCache::get_metrics_snapshot() const {
801
15.2k
    MetricsSnapshot snapshot;
802
1.61M
    for (int i = 0; i < _num_shards; i++) {
803
1.60M
        snapshot.capacity += _shards[i]->get_capacity();
804
1.60M
        snapshot.usage += _shards[i]->get_usage();
805
1.60M
        snapshot.lookup_count += _shards[i]->get_lookup_count();
806
1.60M
        snapshot.hit_count += _shards[i]->get_hit_count();
807
1.60M
        snapshot.miss_count += _shards[i]->get_miss_count();
808
1.60M
        snapshot.stampede_count += _shards[i]->get_stampede_count();
809
1.60M
        snapshot.element_count += _shards[i]->get_element_count();
810
1.60M
    }
811
15.2k
    return snapshot;
812
15.2k
}
813
814
1.24k
void ShardedLRUCache::set_metrics_recorder(std::unique_ptr<MetricsRecorder> metrics_recorder) {
815
1.24k
    std::lock_guard l(_mutex);
816
1.24k
    _metrics_recorder = std::move(metrics_recorder);
817
1.24k
}
818
819
Cache::Handle* DummyLRUCache::insert(const CacheKey& key, void* value, size_t charge,
820
187
                                     CachePriority priority, CacheValueDeleter deleter) {
821
187
    size_t handle_size = sizeof(LRUHandle);
822
187
    auto* e = reinterpret_cast<LRUHandle*>(malloc(handle_size));
823
187
    e->value = value;
824
187
    e->charge = charge;
825
187
    e->key_length = 0;
826
187
    e->total_size = 0;
827
187
    e->hash = 0;
828
187
    e->refs = 1; // only one for the returned handle
829
187
    e->next = e->prev = nullptr;
830
187
    e->in_cache = false;
831
187
    e->deleter = deleter;
832
187
    return reinterpret_cast<Cache::Handle*>(e);
833
187
}
834
835
187
void DummyLRUCache::release(Cache::Handle* handle) {
836
187
    if (handle == nullptr) {
837
0
        return;
838
0
    }
839
187
    auto* e = reinterpret_cast<LRUHandle*>(handle);
840
187
    e->free();
841
187
}
842
843
0
void* DummyLRUCache::value(Handle* handle) {
844
0
    return reinterpret_cast<LRUHandle*>(handle)->value;
845
0
}
846
847
} // namespace doris