@nxtedition/rocksdb 7.0.24 → 7.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binding.cc +3 -1
- package/deps/rocksdb/rocksdb/CMakeLists.txt +5 -0
- package/deps/rocksdb/rocksdb/Makefile +6 -2
- package/deps/rocksdb/rocksdb/TARGETS +14 -0
- package/deps/rocksdb/rocksdb/cache/cache_bench_tool.cc +4 -1
- package/deps/rocksdb/rocksdb/cache/cache_helpers.h +20 -0
- package/deps/rocksdb/rocksdb/cache/cache_reservation_manager_test.cc +2 -2
- package/deps/rocksdb/rocksdb/cache/cache_test.cc +44 -31
- package/deps/rocksdb/rocksdb/cache/clock_cache.cc +491 -722
- package/deps/rocksdb/rocksdb/cache/clock_cache.h +468 -2
- package/deps/rocksdb/rocksdb/cache/compressed_secondary_cache.cc +1 -1
- package/deps/rocksdb/rocksdb/cache/fast_lru_cache.cc +51 -52
- package/deps/rocksdb/rocksdb/cache/fast_lru_cache.h +28 -16
- package/deps/rocksdb/rocksdb/cache/lru_cache.cc +12 -1
- package/deps/rocksdb/rocksdb/cache/lru_cache.h +1 -0
- package/deps/rocksdb/rocksdb/cache/lru_cache_test.cc +170 -36
- package/deps/rocksdb/rocksdb/db/blob/blob_file_cache_test.cc +1 -1
- package/deps/rocksdb/rocksdb/db/blob/blob_file_reader.cc +63 -36
- package/deps/rocksdb/rocksdb/db/blob/blob_file_reader.h +4 -6
- package/deps/rocksdb/rocksdb/db/blob/blob_file_reader_test.cc +57 -38
- package/deps/rocksdb/rocksdb/db/blob/blob_read_request.h +58 -0
- package/deps/rocksdb/rocksdb/db/blob/blob_source.cc +164 -74
- package/deps/rocksdb/rocksdb/db/blob/blob_source.h +42 -29
- package/deps/rocksdb/rocksdb/db/blob/blob_source_test.cc +419 -62
- package/deps/rocksdb/rocksdb/db/blob/db_blob_basic_test.cc +208 -8
- package/deps/rocksdb/rocksdb/db/c.cc +68 -0
- package/deps/rocksdb/rocksdb/db/c_test.c +95 -2
- package/deps/rocksdb/rocksdb/db/column_family.cc +12 -3
- package/deps/rocksdb/rocksdb/db/compaction/compaction.cc +92 -15
- package/deps/rocksdb/rocksdb/db/compaction/compaction.h +76 -4
- package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator.cc +52 -1
- package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator.h +30 -1
- package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc +126 -0
- package/deps/rocksdb/rocksdb/db/compaction/compaction_job.cc +203 -1584
- package/deps/rocksdb/rocksdb/db/compaction/compaction_job.h +93 -26
- package/deps/rocksdb/rocksdb/db/compaction/compaction_job_test.cc +87 -1
- package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.cc +314 -0
- package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.h +328 -0
- package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.cc +32 -6
- package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.h +4 -1
- package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc +7 -3
- package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_level.cc +174 -33
- package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc +474 -7
- package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc +5 -2
- package/deps/rocksdb/rocksdb/db/compaction/compaction_service_job.cc +825 -0
- package/deps/rocksdb/rocksdb/db/compaction/compaction_state.cc +46 -0
- package/deps/rocksdb/rocksdb/db/compaction/compaction_state.h +42 -0
- package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.cc +223 -0
- package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.h +255 -0
- package/deps/rocksdb/rocksdb/db/compaction/tiered_compaction_test.cc +1253 -0
- package/deps/rocksdb/rocksdb/db/corruption_test.cc +32 -8
- package/deps/rocksdb/rocksdb/db/db_basic_test.cc +3 -1
- package/deps/rocksdb/rocksdb/db/db_block_cache_test.cc +13 -8
- package/deps/rocksdb/rocksdb/db/db_bloom_filter_test.cc +376 -0
- package/deps/rocksdb/rocksdb/db/db_compaction_test.cc +103 -78
- package/deps/rocksdb/rocksdb/db/db_impl/db_impl.cc +4 -6
- package/deps/rocksdb/rocksdb/db/db_impl/db_impl.h +0 -8
- package/deps/rocksdb/rocksdb/db/db_impl/db_impl_open.cc +10 -3
- package/deps/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc +21 -6
- package/deps/rocksdb/rocksdb/db/db_impl/db_impl_secondary.h +19 -1
- package/deps/rocksdb/rocksdb/db/db_iter.cc +91 -14
- package/deps/rocksdb/rocksdb/db/db_iter.h +5 -0
- package/deps/rocksdb/rocksdb/db/db_kv_checksum_test.cc +33 -0
- package/deps/rocksdb/rocksdb/db/db_properties_test.cc +79 -0
- package/deps/rocksdb/rocksdb/db/db_range_del_test.cc +2 -0
- package/deps/rocksdb/rocksdb/db/db_test2.cc +1 -1
- package/deps/rocksdb/rocksdb/db/db_wal_test.cc +5 -2
- package/deps/rocksdb/rocksdb/db/db_with_timestamp_basic_test.cc +185 -0
- package/deps/rocksdb/rocksdb/db/dbformat.cc +1 -4
- package/deps/rocksdb/rocksdb/db/dbformat.h +2 -8
- package/deps/rocksdb/rocksdb/db/internal_stats.cc +71 -29
- package/deps/rocksdb/rocksdb/db/internal_stats.h +160 -5
- package/deps/rocksdb/rocksdb/db/log_reader.cc +29 -3
- package/deps/rocksdb/rocksdb/db/log_reader.h +12 -3
- package/deps/rocksdb/rocksdb/db/repair_test.cc +1 -3
- package/deps/rocksdb/rocksdb/db/version_edit.cc +6 -0
- package/deps/rocksdb/rocksdb/db/version_set.cc +93 -129
- package/deps/rocksdb/rocksdb/db/version_set.h +4 -4
- package/deps/rocksdb/rocksdb/db/version_set_sync_and_async.h +2 -2
- package/deps/rocksdb/rocksdb/db/version_set_test.cc +42 -35
- package/deps/rocksdb/rocksdb/db/write_batch.cc +10 -2
- package/deps/rocksdb/rocksdb/db/write_batch_internal.h +4 -1
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc +10 -4
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.h +3 -3
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc +3 -2
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc +4 -0
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h +5 -1
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc +140 -8
- package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h +12 -0
- package/deps/rocksdb/rocksdb/db_stress_tool/multi_ops_txns_stress.cc +46 -7
- package/deps/rocksdb/rocksdb/db_stress_tool/multi_ops_txns_stress.h +7 -0
- package/deps/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc +27 -7
- package/deps/rocksdb/rocksdb/env/composite_env_wrapper.h +8 -0
- package/deps/rocksdb/rocksdb/env/env_posix.cc +14 -0
- package/deps/rocksdb/rocksdb/env/env_test.cc +130 -1
- package/deps/rocksdb/rocksdb/env/fs_posix.cc +7 -1
- package/deps/rocksdb/rocksdb/env/io_posix.cc +18 -50
- package/deps/rocksdb/rocksdb/env/io_posix.h +53 -6
- package/deps/rocksdb/rocksdb/file/file_prefetch_buffer.cc +8 -10
- package/deps/rocksdb/rocksdb/file/file_prefetch_buffer.h +3 -7
- package/deps/rocksdb/rocksdb/file/prefetch_test.cc +239 -259
- package/deps/rocksdb/rocksdb/file/random_access_file_reader.cc +84 -19
- package/deps/rocksdb/rocksdb/file/random_access_file_reader.h +24 -4
- package/deps/rocksdb/rocksdb/include/rocksdb/advanced_options.h +1 -1
- package/deps/rocksdb/rocksdb/include/rocksdb/c.h +31 -1
- package/deps/rocksdb/rocksdb/include/rocksdb/cache.h +11 -7
- package/deps/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h +2 -0
- package/deps/rocksdb/rocksdb/include/rocksdb/db.h +14 -0
- package/deps/rocksdb/rocksdb/include/rocksdb/env.h +20 -0
- package/deps/rocksdb/rocksdb/include/rocksdb/options.h +37 -13
- package/deps/rocksdb/rocksdb/include/rocksdb/perf_context.h +7 -0
- package/deps/rocksdb/rocksdb/include/rocksdb/statistics.h +14 -0
- package/deps/rocksdb/rocksdb/include/rocksdb/threadpool.h +9 -0
- package/deps/rocksdb/rocksdb/include/rocksdb/write_batch.h +13 -13
- package/deps/rocksdb/rocksdb/logging/auto_roll_logger.cc +12 -2
- package/deps/rocksdb/rocksdb/monitoring/perf_context.cc +38 -0
- package/deps/rocksdb/rocksdb/monitoring/statistics.cc +7 -1
- package/deps/rocksdb/rocksdb/port/win/env_win.cc +17 -0
- package/deps/rocksdb/rocksdb/port/win/env_win.h +8 -0
- package/deps/rocksdb/rocksdb/port/win/io_win.cc +6 -3
- package/{prebuilds → deps/rocksdb/rocksdb/prebuilds}/linux-x64/node.napi.node +0 -0
- package/deps/rocksdb/rocksdb/src.mk +5 -0
- package/deps/rocksdb/rocksdb/table/block_based/block.h +1 -2
- package/deps/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc +1 -1
- package/deps/rocksdb/rocksdb/table/block_based/block_based_table_iterator.cc +5 -2
- package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc +1 -1
- package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader_impl.h +15 -12
- package/deps/rocksdb/rocksdb/table/block_based/block_prefetcher.cc +5 -4
- package/deps/rocksdb/rocksdb/table/block_based/block_prefetcher.h +2 -1
- package/deps/rocksdb/rocksdb/table/block_based/filter_policy.cc +1 -1
- package/deps/rocksdb/rocksdb/table/block_based/partitioned_index_iterator.cc +4 -4
- package/deps/rocksdb/rocksdb/table/block_fetcher.cc +1 -2
- package/deps/rocksdb/rocksdb/table/get_context.cc +1 -0
- package/deps/rocksdb/rocksdb/table/sst_file_dumper.cc +1 -2
- package/deps/rocksdb/rocksdb/tools/db_bench_tool.cc +24 -4
- package/deps/rocksdb/rocksdb/util/async_file_reader.cc +1 -1
- package/deps/rocksdb/rocksdb/util/compression.h +2 -0
- package/deps/rocksdb/rocksdb/util/thread_list_test.cc +18 -1
- package/deps/rocksdb/rocksdb/util/threadpool_imp.cc +67 -4
- package/deps/rocksdb/rocksdb/util/threadpool_imp.h +8 -0
- package/deps/rocksdb/rocksdb/utilities/backup/backup_engine.cc +15 -12
- package/deps/rocksdb/rocksdb/utilities/backup/backup_engine_test.cc +4 -2
- package/deps/rocksdb/rocksdb/utilities/simulator_cache/sim_cache_test.cc +1 -1
- package/deps/rocksdb/rocksdb.gyp +5 -1
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
|
@@ -9,831 +9,600 @@
|
|
|
9
9
|
|
|
10
10
|
#include "cache/clock_cache.h"
|
|
11
11
|
|
|
12
|
-
#
|
|
12
|
+
#include <cassert>
|
|
13
|
+
#include <cstdint>
|
|
14
|
+
#include <cstdio>
|
|
15
|
+
#include <functional>
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
17
|
+
#include "monitoring/perf_context_imp.h"
|
|
18
|
+
#include "monitoring/statistics.h"
|
|
19
|
+
#include "port/lang.h"
|
|
20
|
+
#include "util/distributed_mutex.h"
|
|
21
|
+
#include "util/hash.h"
|
|
22
|
+
#include "util/math.h"
|
|
23
|
+
#include "util/random.h"
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
namespace ROCKSDB_NAMESPACE {
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
namespace clock_cache {
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
ClockHandleTable::ClockHandleTable(int hash_bits)
|
|
30
|
+
: length_bits_(hash_bits),
|
|
31
|
+
length_bits_mask_((uint32_t{1} << length_bits_) - 1),
|
|
32
|
+
occupancy_(0),
|
|
33
|
+
occupancy_limit_(static_cast<uint32_t>((uint32_t{1} << length_bits_) *
|
|
34
|
+
kStrictLoadFactor)),
|
|
35
|
+
array_(new ClockHandle[size_t{1} << length_bits_]) {
|
|
36
|
+
assert(hash_bits <= 32);
|
|
37
|
+
}
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#define TBB_USE_EXCEPTIONS 0
|
|
35
|
-
#endif
|
|
36
|
-
#include "cache/sharded_cache.h"
|
|
37
|
-
#include "port/lang.h"
|
|
38
|
-
#include "port/malloc.h"
|
|
39
|
-
#include "port/port.h"
|
|
40
|
-
#include "tbb/concurrent_hash_map.h"
|
|
41
|
-
#include "util/autovector.h"
|
|
42
|
-
#include "util/distributed_mutex.h"
|
|
39
|
+
ClockHandleTable::~ClockHandleTable() {
|
|
40
|
+
ApplyToEntriesRange([](ClockHandle* h) { h->FreeData(); }, 0, GetTableSize());
|
|
41
|
+
}
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
ClockHandle* ClockHandleTable::Lookup(const Slice& key) {
|
|
44
|
+
int probe = 0;
|
|
45
|
+
int slot = FindVisibleElement(key, probe, 0);
|
|
46
|
+
return (slot == -1) ? nullptr : &array_[slot];
|
|
47
|
+
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// has been access since last examine. In contrast to LRU, no modification
|
|
54
|
-
// to the internal data-structure (except for flipping the usage bit) needs
|
|
55
|
-
// to be done upon lookup. This gives us oppertunity to implement a cache
|
|
56
|
-
// with better concurrency.
|
|
57
|
-
//
|
|
58
|
-
// Each cache entry is represented by a cache handle, and all the handles
|
|
59
|
-
// are arranged in a circular list, as describe above. Upon erase of an entry,
|
|
60
|
-
// we never remove the handle. Instead, the handle is put into a recycle bin
|
|
61
|
-
// to be re-use. This is to avoid memory dealocation, which is hard to deal
|
|
62
|
-
// with in concurrent environment.
|
|
63
|
-
//
|
|
64
|
-
// The cache also maintains a concurrent hash map for lookup. Any concurrent
|
|
65
|
-
// hash map implementation should do the work. We currently use
|
|
66
|
-
// tbb::concurrent_hash_map because it supports concurrent erase.
|
|
67
|
-
//
|
|
68
|
-
// Each cache handle has the following flags and counters, which are squeeze
|
|
69
|
-
// in an atomic interger, to make sure the handle always be in a consistent
|
|
70
|
-
// state:
|
|
71
|
-
//
|
|
72
|
-
// * In-cache bit: whether the entry is reference by the cache itself. If
|
|
73
|
-
// an entry is in cache, its key would also be available in the hash map.
|
|
74
|
-
// * Usage bit: whether the entry has been access by user since last
|
|
75
|
-
// examine for eviction. Can be reset by eviction.
|
|
76
|
-
// * Reference count: reference count by user.
|
|
77
|
-
//
|
|
78
|
-
// An entry can be reference only when it's in cache. An entry can be evicted
|
|
79
|
-
// only when it is in cache, has no usage since last examine, and reference
|
|
80
|
-
// count is zero.
|
|
81
|
-
//
|
|
82
|
-
// The follow figure shows a possible layout of the cache. Boxes represents
|
|
83
|
-
// cache handles and numbers in each box being in-cache bit, usage bit and
|
|
84
|
-
// reference count respectively.
|
|
85
|
-
//
|
|
86
|
-
// hash map:
|
|
87
|
-
// +-------+--------+
|
|
88
|
-
// | key | handle |
|
|
89
|
-
// +-------+--------+
|
|
90
|
-
// | "foo" | 5 |-------------------------------------+
|
|
91
|
-
// +-------+--------+ |
|
|
92
|
-
// | "bar" | 2 |--+ |
|
|
93
|
-
// +-------+--------+ | |
|
|
94
|
-
// | |
|
|
95
|
-
// head | |
|
|
96
|
-
// | | |
|
|
97
|
-
// circular list: | | |
|
|
98
|
-
// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
|
|
99
|
-
// |(0,0,0)|---|(1,1,0)|---|(0,0,0)|---|(0,1,3)|---|(1,0,0)|---| ...
|
|
100
|
-
// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
|
|
101
|
-
// | |
|
|
102
|
-
// +-------+ +-----------+
|
|
103
|
-
// | |
|
|
104
|
-
// +---+---+
|
|
105
|
-
// recycle bin: | 1 | 3 |
|
|
106
|
-
// +---+---+
|
|
107
|
-
//
|
|
108
|
-
// Suppose we try to insert "baz" into the cache at this point and the cache is
|
|
109
|
-
// full. The cache will first look for entries to evict, starting from where
|
|
110
|
-
// head points to (the second entry). It resets usage bit of the second entry,
|
|
111
|
-
// skips the third and fourth entry since they are not in cache, and finally
|
|
112
|
-
// evict the fifth entry ("foo"). It looks at recycle bin for available handle,
|
|
113
|
-
// grabs handle 3, and insert the key into the handle. The following figure
|
|
114
|
-
// shows the resulting layout.
|
|
115
|
-
//
|
|
116
|
-
// hash map:
|
|
117
|
-
// +-------+--------+
|
|
118
|
-
// | key | handle |
|
|
119
|
-
// +-------+--------+
|
|
120
|
-
// | "baz" | 3 |-------------+
|
|
121
|
-
// +-------+--------+ |
|
|
122
|
-
// | "bar" | 2 |--+ |
|
|
123
|
-
// +-------+--------+ | |
|
|
124
|
-
// | |
|
|
125
|
-
// | | head
|
|
126
|
-
// | | |
|
|
127
|
-
// circular list: | | |
|
|
128
|
-
// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
|
|
129
|
-
// |(0,0,0)|---|(1,0,0)|---|(1,0,0)|---|(0,1,3)|---|(0,0,0)|---| ...
|
|
130
|
-
// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
|
|
131
|
-
// | |
|
|
132
|
-
// +-------+ +-----------------------------------+
|
|
133
|
-
// | |
|
|
134
|
-
// +---+---+
|
|
135
|
-
// recycle bin: | 1 | 5 |
|
|
136
|
-
// +---+---+
|
|
137
|
-
//
|
|
138
|
-
// A global mutex guards the circular list, the head, and the recycle bin.
|
|
139
|
-
// We additionally require that modifying the hash map needs to hold the mutex.
|
|
140
|
-
// As such, Modifying the cache (such as Insert() and Erase()) require to
|
|
141
|
-
// hold the mutex. Lookup() only access the hash map and the flags associated
|
|
142
|
-
// with each handle, and don't require explicit locking. Release() has to
|
|
143
|
-
// acquire the mutex only when it releases the last reference to the entry and
|
|
144
|
-
// the entry has been erased from cache explicitly. A future improvement could
|
|
145
|
-
// be to remove the mutex completely.
|
|
146
|
-
//
|
|
147
|
-
// Benchmark:
|
|
148
|
-
// We run readrandom db_bench on a test DB of size 13GB, with size of each
|
|
149
|
-
// level:
|
|
150
|
-
//
|
|
151
|
-
// Level Files Size(MB)
|
|
152
|
-
// -------------------------
|
|
153
|
-
// L0 1 0.01
|
|
154
|
-
// L1 18 17.32
|
|
155
|
-
// L2 230 182.94
|
|
156
|
-
// L3 1186 1833.63
|
|
157
|
-
// L4 4602 8140.30
|
|
158
|
-
//
|
|
159
|
-
// We test with both 32 and 16 read threads, with 2GB cache size (the whole DB
|
|
160
|
-
// doesn't fits in) and 64GB cache size (the whole DB can fit in cache), and
|
|
161
|
-
// whether to put index and filter blocks in block cache. The benchmark runs
|
|
162
|
-
// with
|
|
163
|
-
// with RocksDB 4.10. We got the following result:
|
|
164
|
-
//
|
|
165
|
-
// Threads Cache Cache ClockCache LRUCache
|
|
166
|
-
// Size Index/Filter Throughput(MB/s) Hit Throughput(MB/s) Hit
|
|
167
|
-
// 32 2GB yes 466.7 85.9% 433.7 86.5%
|
|
168
|
-
// 32 2GB no 529.9 72.7% 532.7 73.9%
|
|
169
|
-
// 32 64GB yes 649.9 99.9% 507.9 99.9%
|
|
170
|
-
// 32 64GB no 740.4 99.9% 662.8 99.9%
|
|
171
|
-
// 16 2GB yes 278.4 85.9% 283.4 86.5%
|
|
172
|
-
// 16 2GB no 318.6 72.7% 335.8 73.9%
|
|
173
|
-
// 16 64GB yes 391.9 99.9% 353.3 99.9%
|
|
174
|
-
// 16 64GB no 433.8 99.8% 419.4 99.8%
|
|
175
|
-
|
|
176
|
-
// Cache entry meta data.
|
|
177
|
-
struct CacheHandle {
|
|
178
|
-
Slice key;
|
|
179
|
-
void* value;
|
|
180
|
-
size_t charge;
|
|
181
|
-
Cache::DeleterFn deleter;
|
|
182
|
-
uint32_t hash;
|
|
183
|
-
|
|
184
|
-
// Addition to "charge" to get "total charge" under metadata policy.
|
|
185
|
-
uint32_t meta_charge;
|
|
186
|
-
|
|
187
|
-
// Flags and counters associated with the cache handle:
|
|
188
|
-
// lowest bit: in-cache bit
|
|
189
|
-
// second lowest bit: usage bit
|
|
190
|
-
// the rest bits: reference count
|
|
191
|
-
// The handle is unused when flags equals to 0. The thread decreases the count
|
|
192
|
-
// to 0 is responsible to put the handle back to recycle_ and cleanup memory.
|
|
193
|
-
std::atomic<uint32_t> flags;
|
|
194
|
-
|
|
195
|
-
CacheHandle() = default;
|
|
196
|
-
|
|
197
|
-
CacheHandle(const CacheHandle& a) { *this = a; }
|
|
198
|
-
|
|
199
|
-
CacheHandle(const Slice& k, void* v,
|
|
200
|
-
void (*del)(const Slice& key, void* value))
|
|
201
|
-
: key(k), value(v), deleter(del) {}
|
|
202
|
-
|
|
203
|
-
CacheHandle& operator=(const CacheHandle& a) {
|
|
204
|
-
// Only copy members needed for deletion.
|
|
205
|
-
key = a.key;
|
|
206
|
-
value = a.value;
|
|
207
|
-
deleter = a.deleter;
|
|
208
|
-
return *this;
|
|
49
|
+
ClockHandle* ClockHandleTable::Insert(ClockHandle* h, ClockHandle** old) {
|
|
50
|
+
int probe = 0;
|
|
51
|
+
int slot =
|
|
52
|
+
FindVisibleElementOrAvailableSlot(h->key(), probe, 1 /*displacement*/);
|
|
53
|
+
*old = nullptr;
|
|
54
|
+
if (slot == -1) {
|
|
55
|
+
return nullptr;
|
|
209
56
|
}
|
|
210
57
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
58
|
+
if (array_[slot].IsEmpty() || array_[slot].IsTombstone()) {
|
|
59
|
+
bool empty = array_[slot].IsEmpty();
|
|
60
|
+
Assign(slot, h);
|
|
61
|
+
ClockHandle* new_entry = &array_[slot];
|
|
62
|
+
if (empty) {
|
|
63
|
+
// This used to be an empty slot.
|
|
64
|
+
return new_entry;
|
|
65
|
+
}
|
|
66
|
+
// It used to be a tombstone, so there may already be a copy of the
|
|
67
|
+
// key in the table.
|
|
68
|
+
slot = FindVisibleElement(h->key(), probe, 0 /*displacement*/);
|
|
69
|
+
if (slot == -1) {
|
|
70
|
+
// No existing copy of the key.
|
|
71
|
+
return new_entry;
|
|
222
72
|
}
|
|
223
|
-
|
|
224
|
-
return
|
|
73
|
+
*old = &array_[slot];
|
|
74
|
+
return new_entry;
|
|
75
|
+
} else {
|
|
76
|
+
// There is an existing copy of the key.
|
|
77
|
+
*old = &array_[slot];
|
|
78
|
+
// Find an available slot for the new element.
|
|
79
|
+
array_[slot].displacements++;
|
|
80
|
+
slot = FindAvailableSlot(h->key(), probe, 1 /*displacement*/);
|
|
81
|
+
if (slot == -1) {
|
|
82
|
+
// No available slots. Roll back displacements.
|
|
83
|
+
probe = 0;
|
|
84
|
+
slot = FindVisibleElement(h->key(), probe, -1);
|
|
85
|
+
array_[slot].displacements--;
|
|
86
|
+
FindAvailableSlot(h->key(), probe, -1);
|
|
87
|
+
return nullptr;
|
|
88
|
+
}
|
|
89
|
+
Assign(slot, h);
|
|
90
|
+
return &array_[slot];
|
|
225
91
|
}
|
|
92
|
+
}
|
|
226
93
|
|
|
227
|
-
|
|
228
|
-
|
|
94
|
+
void ClockHandleTable::Remove(ClockHandle* h) {
|
|
95
|
+
assert(!h->IsInClockList()); // Already off the clock list.
|
|
96
|
+
int probe = 0;
|
|
97
|
+
FindSlot(
|
|
98
|
+
h->key(), [&h](ClockHandle* e) { return e == h; }, probe,
|
|
99
|
+
-1 /*displacement*/);
|
|
100
|
+
h->SetIsVisible(false);
|
|
101
|
+
h->SetIsElement(false);
|
|
102
|
+
occupancy_--;
|
|
103
|
+
}
|
|
229
104
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
105
|
+
void ClockHandleTable::Assign(int slot, ClockHandle* h) {
|
|
106
|
+
ClockHandle* dst = &array_[slot];
|
|
107
|
+
uint32_t disp = dst->displacements;
|
|
108
|
+
*dst = *h;
|
|
109
|
+
dst->displacements = disp;
|
|
110
|
+
dst->SetIsVisible(true);
|
|
111
|
+
dst->SetIsElement(true);
|
|
112
|
+
dst->SetClockPriority(ClockHandle::ClockPriority::NONE);
|
|
113
|
+
occupancy_++;
|
|
114
|
+
}
|
|
234
115
|
|
|
235
|
-
|
|
116
|
+
void ClockHandleTable::Exclude(ClockHandle* h) { h->SetIsVisible(false); }
|
|
236
117
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
118
|
+
int ClockHandleTable::FindVisibleElement(const Slice& key, int& probe,
|
|
119
|
+
int displacement) {
|
|
120
|
+
return FindSlot(
|
|
121
|
+
key, [&](ClockHandle* h) { return h->Matches(key) && h->IsVisible(); },
|
|
122
|
+
probe, displacement);
|
|
123
|
+
}
|
|
241
124
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
125
|
+
int ClockHandleTable::FindAvailableSlot(const Slice& key, int& probe,
|
|
126
|
+
int displacement) {
|
|
127
|
+
return FindSlot(
|
|
128
|
+
key, [](ClockHandle* h) { return h->IsEmpty() || h->IsTombstone(); },
|
|
129
|
+
probe, displacement);
|
|
130
|
+
}
|
|
245
131
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
|
281
|
-
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
|
282
|
-
const Cache::CacheItemHelper* /*helper*/,
|
|
283
|
-
const Cache::CreateCallback& /*create_cb*/,
|
|
284
|
-
Cache::Priority /*priority*/, bool /*wait*/,
|
|
285
|
-
Statistics* /*stats*/) override {
|
|
286
|
-
return Lookup(key, hash);
|
|
287
|
-
}
|
|
288
|
-
bool Release(Cache::Handle* handle, bool /*useful*/,
|
|
289
|
-
bool erase_if_last_ref) override {
|
|
290
|
-
return Release(handle, erase_if_last_ref);
|
|
291
|
-
}
|
|
292
|
-
bool IsReady(Cache::Handle* /*handle*/) override { return true; }
|
|
293
|
-
void Wait(Cache::Handle* /*handle*/) override {}
|
|
294
|
-
|
|
295
|
-
// If the entry in in cache, increase reference count and return true.
|
|
296
|
-
// Return false otherwise.
|
|
297
|
-
//
|
|
298
|
-
// Not necessary to hold mutex_ before being called.
|
|
299
|
-
bool Ref(Cache::Handle* handle) override;
|
|
300
|
-
bool Release(Cache::Handle* handle, bool erase_if_last_ref = false) override;
|
|
301
|
-
void Erase(const Slice& key, uint32_t hash) override;
|
|
302
|
-
bool EraseAndConfirm(const Slice& key, uint32_t hash,
|
|
303
|
-
CleanupContext* context);
|
|
304
|
-
size_t GetUsage() const override;
|
|
305
|
-
size_t GetPinnedUsage() const override;
|
|
306
|
-
void EraseUnRefEntries() override;
|
|
307
|
-
void ApplyToSomeEntries(
|
|
308
|
-
const std::function<void(const Slice& key, void* value, size_t charge,
|
|
309
|
-
DeleterFn deleter)>& callback,
|
|
310
|
-
uint32_t average_entries_per_lock, uint32_t* state) override;
|
|
311
|
-
|
|
312
|
-
private:
|
|
313
|
-
static const uint32_t kInCacheBit = 1;
|
|
314
|
-
static const uint32_t kUsageBit = 2;
|
|
315
|
-
static const uint32_t kRefsOffset = 2;
|
|
316
|
-
static const uint32_t kOneRef = 1 << kRefsOffset;
|
|
317
|
-
|
|
318
|
-
// Helper functions to extract cache handle flags and counters.
|
|
319
|
-
static bool InCache(uint32_t flags) { return flags & kInCacheBit; }
|
|
320
|
-
static bool HasUsage(uint32_t flags) { return flags & kUsageBit; }
|
|
321
|
-
static uint32_t CountRefs(uint32_t flags) { return flags >> kRefsOffset; }
|
|
322
|
-
|
|
323
|
-
// Decrease reference count of the entry. If this decreases the count to 0,
|
|
324
|
-
// recycle the entry. If set_usage is true, also set the usage bit.
|
|
325
|
-
//
|
|
326
|
-
// returns true if a value is erased.
|
|
327
|
-
//
|
|
328
|
-
// Not necessary to hold mutex_ before being called.
|
|
329
|
-
bool Unref(CacheHandle* handle, bool set_usage, CleanupContext* context);
|
|
330
|
-
|
|
331
|
-
// Unset in-cache bit of the entry. Recycle the handle if necessary.
|
|
332
|
-
//
|
|
333
|
-
// returns true if a value is erased.
|
|
334
|
-
//
|
|
335
|
-
// Has to hold mutex_ before being called.
|
|
336
|
-
bool UnsetInCache(CacheHandle* handle, CleanupContext* context);
|
|
337
|
-
|
|
338
|
-
// Put the handle back to recycle_ list, and put the value associated with
|
|
339
|
-
// it into to-be-deleted list. It doesn't cleanup the key as it might be
|
|
340
|
-
// reused by another handle.
|
|
341
|
-
//
|
|
342
|
-
// Has to hold mutex_ before being called.
|
|
343
|
-
void RecycleHandle(CacheHandle* handle, CleanupContext* context);
|
|
344
|
-
|
|
345
|
-
// Delete keys and values in to-be-deleted list. Call the method without
|
|
346
|
-
// holding mutex, as destructors can be expensive.
|
|
347
|
-
void Cleanup(const CleanupContext& context);
|
|
348
|
-
|
|
349
|
-
// Examine the handle for eviction. If the handle is in cache, usage bit is
|
|
350
|
-
// not set, and referece count is 0, evict it from cache. Otherwise unset
|
|
351
|
-
// the usage bit.
|
|
352
|
-
//
|
|
353
|
-
// Has to hold mutex_ before being called.
|
|
354
|
-
bool TryEvict(CacheHandle* value, CleanupContext* context);
|
|
355
|
-
|
|
356
|
-
// Scan through the circular list, evict entries until we get enough capacity
|
|
357
|
-
// for new cache entry of specific size. Return true if success, false
|
|
358
|
-
// otherwise.
|
|
359
|
-
//
|
|
360
|
-
// Has to hold mutex_ before being called.
|
|
361
|
-
bool EvictFromCache(size_t charge, CleanupContext* context);
|
|
362
|
-
|
|
363
|
-
CacheHandle* Insert(const Slice& key, uint32_t hash, void* value,
|
|
364
|
-
size_t change,
|
|
365
|
-
void (*deleter)(const Slice& key, void* value),
|
|
366
|
-
bool hold_reference, CleanupContext* context,
|
|
367
|
-
bool* overwritten);
|
|
368
|
-
|
|
369
|
-
// Guards list_, head_, and recycle_. In addition, updating table_ also has
|
|
370
|
-
// to hold the mutex, to avoid the cache being in inconsistent state.
|
|
371
|
-
mutable DMutex mutex_;
|
|
372
|
-
|
|
373
|
-
// The circular list of cache handles. Initially the list is empty. Once a
|
|
374
|
-
// handle is needed by insertion, and no more handles are available in
|
|
375
|
-
// recycle bin, one more handle is appended to the end.
|
|
376
|
-
//
|
|
377
|
-
// We use std::deque for the circular list because we want to make sure
|
|
378
|
-
// pointers to handles are valid through out the life-cycle of the cache
|
|
379
|
-
// (in contrast to std::vector), and be able to grow the list (in contrast
|
|
380
|
-
// to statically allocated arrays).
|
|
381
|
-
std::deque<CacheHandle> list_;
|
|
382
|
-
|
|
383
|
-
// Pointer to the next handle in the circular list to be examine for
|
|
384
|
-
// eviction.
|
|
385
|
-
size_t head_;
|
|
386
|
-
|
|
387
|
-
// Recycle bin of cache handles.
|
|
388
|
-
autovector<CacheHandle*> recycle_;
|
|
389
|
-
|
|
390
|
-
// Maximum cache size.
|
|
391
|
-
std::atomic<size_t> capacity_;
|
|
392
|
-
|
|
393
|
-
// Current total size of the cache.
|
|
394
|
-
std::atomic<size_t> usage_;
|
|
395
|
-
|
|
396
|
-
// Total un-released cache size.
|
|
397
|
-
std::atomic<size_t> pinned_usage_;
|
|
398
|
-
|
|
399
|
-
// Whether allow insert into cache if cache is full.
|
|
400
|
-
std::atomic<bool> strict_capacity_limit_;
|
|
401
|
-
|
|
402
|
-
// Hash table (tbb::concurrent_hash_map) for lookup.
|
|
403
|
-
HashTable table_;
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
ClockCacheShard::ClockCacheShard()
|
|
407
|
-
: head_(0), usage_(0), pinned_usage_(0), strict_capacity_limit_(false) {}
|
|
408
|
-
|
|
409
|
-
ClockCacheShard::~ClockCacheShard() {
|
|
410
|
-
for (auto& handle : list_) {
|
|
411
|
-
uint32_t flags = handle.flags.load(std::memory_order_relaxed);
|
|
412
|
-
if (InCache(flags) || CountRefs(flags) > 0) {
|
|
413
|
-
if (handle.deleter != nullptr) {
|
|
414
|
-
(*handle.deleter)(handle.key, handle.value);
|
|
415
|
-
}
|
|
416
|
-
delete[] handle.key.data();
|
|
132
|
+
int ClockHandleTable::FindVisibleElementOrAvailableSlot(const Slice& key,
|
|
133
|
+
int& probe,
|
|
134
|
+
int displacement) {
|
|
135
|
+
return FindSlot(
|
|
136
|
+
key,
|
|
137
|
+
[&](ClockHandle* h) {
|
|
138
|
+
return h->IsEmpty() || h->IsTombstone() ||
|
|
139
|
+
(h->Matches(key) && h->IsVisible());
|
|
140
|
+
},
|
|
141
|
+
probe, displacement);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
inline int ClockHandleTable::FindSlot(const Slice& key,
|
|
145
|
+
std::function<bool(ClockHandle*)> cond,
|
|
146
|
+
int& probe, int displacement) {
|
|
147
|
+
uint32_t base = ModTableSize(Hash(key.data(), key.size(), kProbingSeed1));
|
|
148
|
+
uint32_t increment =
|
|
149
|
+
ModTableSize((Hash(key.data(), key.size(), kProbingSeed2) << 1) | 1);
|
|
150
|
+
uint32_t current = ModTableSize(base + probe * increment);
|
|
151
|
+
while (true) {
|
|
152
|
+
ClockHandle* h = &array_[current];
|
|
153
|
+
probe++;
|
|
154
|
+
if (current == base && probe > 1) {
|
|
155
|
+
// We looped back.
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
if (cond(h)) {
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
if (h->IsEmpty()) {
|
|
162
|
+
// We check emptyness after the condition, because
|
|
163
|
+
// the condition may be emptyness.
|
|
164
|
+
return -1;
|
|
417
165
|
}
|
|
166
|
+
h->displacements += displacement;
|
|
167
|
+
current = ModTableSize(current + increment);
|
|
418
168
|
}
|
|
419
169
|
}
|
|
420
170
|
|
|
421
|
-
|
|
422
|
-
|
|
171
|
+
ClockCacheShard::ClockCacheShard(
|
|
172
|
+
size_t capacity, size_t estimated_value_size, bool strict_capacity_limit,
|
|
173
|
+
CacheMetadataChargePolicy metadata_charge_policy)
|
|
174
|
+
: capacity_(capacity),
|
|
175
|
+
strict_capacity_limit_(strict_capacity_limit),
|
|
176
|
+
clock_pointer_(0),
|
|
177
|
+
table_(
|
|
178
|
+
CalcHashBits(capacity, estimated_value_size, metadata_charge_policy)),
|
|
179
|
+
usage_(0),
|
|
180
|
+
clock_usage_(0) {
|
|
181
|
+
set_metadata_charge_policy(metadata_charge_policy);
|
|
423
182
|
}
|
|
424
183
|
|
|
425
|
-
|
|
426
|
-
|
|
184
|
+
void ClockCacheShard::EraseUnRefEntries() {
|
|
185
|
+
autovector<ClockHandle> last_reference_list;
|
|
186
|
+
{
|
|
187
|
+
DMutexLock l(mutex_);
|
|
188
|
+
uint32_t slot = 0;
|
|
189
|
+
do {
|
|
190
|
+
ClockHandle* old = &(table_.array_[slot]);
|
|
191
|
+
if (!old->IsInClockList()) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
ClockRemove(old);
|
|
195
|
+
table_.Remove(old);
|
|
196
|
+
assert(usage_ >= old->total_charge);
|
|
197
|
+
usage_ -= old->total_charge;
|
|
198
|
+
last_reference_list.push_back(*old);
|
|
199
|
+
slot = table_.ModTableSize(slot + 1);
|
|
200
|
+
} while (slot != 0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Free the entries here outside of mutex for performance reasons.
|
|
204
|
+
for (auto& h : last_reference_list) {
|
|
205
|
+
h.FreeData();
|
|
206
|
+
}
|
|
427
207
|
}
|
|
428
208
|
|
|
429
209
|
void ClockCacheShard::ApplyToSomeEntries(
|
|
430
210
|
const std::function<void(const Slice& key, void* value, size_t charge,
|
|
431
211
|
DeleterFn deleter)>& callback,
|
|
432
212
|
uint32_t average_entries_per_lock, uint32_t* state) {
|
|
433
|
-
|
|
213
|
+
// The state is essentially going to be the starting hash, which works
|
|
214
|
+
// nicely even if we resize between calls because we use upper-most
|
|
215
|
+
// hash bits for table indexes.
|
|
434
216
|
DMutexLock l(mutex_);
|
|
217
|
+
uint32_t length_bits = table_.GetLengthBits();
|
|
218
|
+
uint32_t length = table_.GetTableSize();
|
|
435
219
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
if (end_idx >= list_size || end_idx >= UINT32_MAX) {
|
|
448
|
-
// This also includes the hypothetical case of >4 billion
|
|
449
|
-
// cache handles.
|
|
450
|
-
end_idx = list_size;
|
|
451
|
-
// Mark finished with all
|
|
220
|
+
assert(average_entries_per_lock > 0);
|
|
221
|
+
// Assuming we are called with same average_entries_per_lock repeatedly,
|
|
222
|
+
// this simplifies some logic (index_end will not overflow).
|
|
223
|
+
assert(average_entries_per_lock < length || *state == 0);
|
|
224
|
+
|
|
225
|
+
uint32_t index_begin = *state >> (32 - length_bits);
|
|
226
|
+
uint32_t index_end = index_begin + average_entries_per_lock;
|
|
227
|
+
if (index_end >= length) {
|
|
228
|
+
// Going to end
|
|
229
|
+
index_end = length;
|
|
452
230
|
*state = UINT32_MAX;
|
|
453
231
|
} else {
|
|
454
|
-
*state =
|
|
232
|
+
*state = index_end << (32 - length_bits);
|
|
455
233
|
}
|
|
456
234
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
uint32_t flags = handle.flags.load(std::memory_order_relaxed);
|
|
465
|
-
if (InCache(flags)) {
|
|
466
|
-
callback(handle.key, handle.value, handle.charge, handle.deleter);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
235
|
+
table_.ApplyToEntriesRange(
|
|
236
|
+
[callback,
|
|
237
|
+
metadata_charge_policy = metadata_charge_policy_](ClockHandle* h) {
|
|
238
|
+
callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
|
|
239
|
+
h->deleter);
|
|
240
|
+
},
|
|
241
|
+
index_begin, index_end);
|
|
469
242
|
}
|
|
470
243
|
|
|
471
|
-
void ClockCacheShard::
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
assert(
|
|
475
|
-
|
|
476
|
-
context->to_delete_value.emplace_back(*handle);
|
|
477
|
-
size_t total_charge = handle->GetTotalCharge();
|
|
478
|
-
// clearing `handle` fields would go here but not strictly required
|
|
479
|
-
recycle_.push_back(handle);
|
|
480
|
-
usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
|
244
|
+
void ClockCacheShard::ClockRemove(ClockHandle* h) {
|
|
245
|
+
assert(h->IsInClockList());
|
|
246
|
+
h->SetClockPriority(ClockHandle::ClockPriority::NONE);
|
|
247
|
+
assert(clock_usage_ >= h->total_charge);
|
|
248
|
+
clock_usage_ -= h->total_charge;
|
|
481
249
|
}
|
|
482
250
|
|
|
483
|
-
void ClockCacheShard::
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
251
|
+
void ClockCacheShard::ClockInsert(ClockHandle* h) {
|
|
252
|
+
assert(!h->IsInClockList());
|
|
253
|
+
bool is_high_priority =
|
|
254
|
+
h->HasHit() || h->GetCachePriority() == Cache::Priority::HIGH;
|
|
255
|
+
h->SetClockPriority(static_cast<ClockHandle::ClockPriority>(
|
|
256
|
+
is_high_priority * ClockHandle::ClockPriority::HIGH +
|
|
257
|
+
(1 - is_high_priority) * ClockHandle::ClockPriority::MEDIUM));
|
|
258
|
+
clock_usage_ += h->total_charge;
|
|
492
259
|
}
|
|
493
260
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
//
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
std::memory_order_relaxed)) {
|
|
504
|
-
if (CountRefs(flags) == 0) {
|
|
505
|
-
// No reference count before the operation.
|
|
506
|
-
size_t total_charge = handle->GetTotalCharge();
|
|
507
|
-
pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
|
508
|
-
}
|
|
509
|
-
return true;
|
|
261
|
+
void ClockCacheShard::EvictFromClock(size_t charge,
|
|
262
|
+
autovector<ClockHandle>* deleted) {
|
|
263
|
+
assert(charge <= capacity_);
|
|
264
|
+
while (clock_usage_ > 0 && (usage_ + charge) > capacity_) {
|
|
265
|
+
ClockHandle* old = &table_.array_[clock_pointer_];
|
|
266
|
+
clock_pointer_ = table_.ModTableSize(clock_pointer_ + 1);
|
|
267
|
+
// Clock list contains only elements which can be evicted.
|
|
268
|
+
if (!old->IsInClockList()) {
|
|
269
|
+
continue;
|
|
510
270
|
}
|
|
271
|
+
if (old->GetClockPriority() == ClockHandle::ClockPriority::LOW) {
|
|
272
|
+
ClockRemove(old);
|
|
273
|
+
table_.Remove(old);
|
|
274
|
+
assert(usage_ >= old->total_charge);
|
|
275
|
+
usage_ -= old->total_charge;
|
|
276
|
+
deleted->push_back(*old);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
old->DecreaseClockPriority();
|
|
511
280
|
}
|
|
512
|
-
return false;
|
|
513
281
|
}
|
|
514
282
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
// atomic operation then we cannot access `handle` afterward, because
|
|
522
|
-
// it could be evicted before we access the `handle`.
|
|
523
|
-
size_t total_charge = handle->GetTotalCharge();
|
|
524
|
-
|
|
525
|
-
// Use acquire-release semantics as previous operations on the cache entry
|
|
526
|
-
// has to be order before reference count is decreased, and potential cleanup
|
|
527
|
-
// of the entry has to be order after.
|
|
528
|
-
uint32_t flags = handle->flags.fetch_sub(kOneRef, std::memory_order_acq_rel);
|
|
529
|
-
assert(CountRefs(flags) > 0);
|
|
530
|
-
if (CountRefs(flags) == 1) {
|
|
531
|
-
// this is the last reference.
|
|
532
|
-
pinned_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
|
533
|
-
// Cleanup if it is the last reference.
|
|
534
|
-
if (!InCache(flags)) {
|
|
535
|
-
DMutexLock l(mutex_);
|
|
536
|
-
RecycleHandle(handle, context);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return context->to_delete_value.size();
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
bool ClockCacheShard::UnsetInCache(CacheHandle* handle,
|
|
543
|
-
CleanupContext* context) {
|
|
544
|
-
mutex_.AssertHeld();
|
|
545
|
-
// Use acquire-release semantics as previous operations on the cache entry
|
|
546
|
-
// has to be order before reference count is decreased, and potential cleanup
|
|
547
|
-
// of the entry has to be order after.
|
|
548
|
-
uint32_t flags =
|
|
549
|
-
handle->flags.fetch_and(~kInCacheBit, std::memory_order_acq_rel);
|
|
550
|
-
// Cleanup if it is the last reference.
|
|
551
|
-
if (InCache(flags) && CountRefs(flags) == 0) {
|
|
552
|
-
RecycleHandle(handle, context);
|
|
553
|
-
}
|
|
554
|
-
return context->to_delete_value.size();
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
bool ClockCacheShard::TryEvict(CacheHandle* handle, CleanupContext* context) {
|
|
558
|
-
mutex_.AssertHeld();
|
|
559
|
-
uint32_t flags = kInCacheBit;
|
|
560
|
-
if (handle->flags.compare_exchange_strong(flags, 0, std::memory_order_acquire,
|
|
561
|
-
std::memory_order_relaxed)) {
|
|
562
|
-
bool erased __attribute__((__unused__)) =
|
|
563
|
-
table_.erase(ClockCacheKey(handle->key, handle->hash));
|
|
564
|
-
assert(erased);
|
|
565
|
-
RecycleHandle(handle, context);
|
|
566
|
-
return true;
|
|
567
|
-
}
|
|
568
|
-
handle->flags.fetch_and(~kUsageBit, std::memory_order_relaxed);
|
|
569
|
-
return false;
|
|
283
|
+
size_t ClockCacheShard::CalcEstimatedHandleCharge(
|
|
284
|
+
size_t estimated_value_size,
|
|
285
|
+
CacheMetadataChargePolicy metadata_charge_policy) {
|
|
286
|
+
ClockHandle h;
|
|
287
|
+
h.CalcTotalCharge(estimated_value_size, metadata_charge_policy);
|
|
288
|
+
return h.total_charge;
|
|
570
289
|
}
|
|
571
290
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (TryEvict(&list_[new_head], context)) {
|
|
583
|
-
usage = usage_.load(std::memory_order_relaxed);
|
|
584
|
-
}
|
|
585
|
-
new_head = (new_head + 1 >= list_.size()) ? 0 : new_head + 1;
|
|
586
|
-
if (new_head == head_) {
|
|
587
|
-
if (second_iteration) {
|
|
588
|
-
return false;
|
|
589
|
-
} else {
|
|
590
|
-
second_iteration = true;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
head_ = new_head;
|
|
595
|
-
return true;
|
|
291
|
+
int ClockCacheShard::CalcHashBits(
|
|
292
|
+
size_t capacity, size_t estimated_value_size,
|
|
293
|
+
CacheMetadataChargePolicy metadata_charge_policy) {
|
|
294
|
+
size_t handle_charge =
|
|
295
|
+
CalcEstimatedHandleCharge(estimated_value_size, metadata_charge_policy);
|
|
296
|
+
assert(handle_charge > 0);
|
|
297
|
+
uint32_t num_entries =
|
|
298
|
+
static_cast<uint32_t>(capacity / (kLoadFactor * handle_charge)) + 1;
|
|
299
|
+
assert(num_entries <= uint32_t{1} << 31);
|
|
300
|
+
return FloorLog2((num_entries << 1) - 1);
|
|
596
301
|
}
|
|
597
302
|
|
|
598
303
|
void ClockCacheShard::SetCapacity(size_t capacity) {
|
|
599
|
-
|
|
304
|
+
assert(false); // Not supported. TODO(Guido) Support it?
|
|
305
|
+
autovector<ClockHandle> last_reference_list;
|
|
600
306
|
{
|
|
601
307
|
DMutexLock l(mutex_);
|
|
602
|
-
capacity_
|
|
603
|
-
|
|
308
|
+
capacity_ = capacity;
|
|
309
|
+
EvictFromClock(0, &last_reference_list);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Free the entries here outside of mutex for performance reasons.
|
|
313
|
+
for (auto& h : last_reference_list) {
|
|
314
|
+
h.FreeData();
|
|
604
315
|
}
|
|
605
|
-
Cleanup(context);
|
|
606
316
|
}
|
|
607
317
|
|
|
608
318
|
void ClockCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
|
609
|
-
strict_capacity_limit_.store(strict_capacity_limit,
|
|
610
|
-
std::memory_order_relaxed);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
CacheHandle* ClockCacheShard::Insert(
|
|
614
|
-
const Slice& key, uint32_t hash, void* value, size_t charge,
|
|
615
|
-
void (*deleter)(const Slice& key, void* value), bool hold_reference,
|
|
616
|
-
CleanupContext* context, bool* overwritten) {
|
|
617
|
-
assert(overwritten != nullptr && *overwritten == false);
|
|
618
|
-
uint32_t meta_charge =
|
|
619
|
-
CacheHandle::CalcMetadataCharge(key, metadata_charge_policy_);
|
|
620
|
-
size_t total_charge = charge + meta_charge;
|
|
621
319
|
DMutexLock l(mutex_);
|
|
622
|
-
|
|
623
|
-
bool strict = strict_capacity_limit_.load(std::memory_order_relaxed);
|
|
624
|
-
if (!success && (strict || !hold_reference)) {
|
|
625
|
-
context->to_delete_key.push_back(key.data());
|
|
626
|
-
if (!hold_reference) {
|
|
627
|
-
context->to_delete_value.emplace_back(key, value, deleter);
|
|
628
|
-
}
|
|
629
|
-
return nullptr;
|
|
630
|
-
}
|
|
631
|
-
// Grab available handle from recycle bin. If recycle bin is empty, create
|
|
632
|
-
// and append new handle to end of circular list.
|
|
633
|
-
CacheHandle* handle = nullptr;
|
|
634
|
-
if (!recycle_.empty()) {
|
|
635
|
-
handle = recycle_.back();
|
|
636
|
-
recycle_.pop_back();
|
|
637
|
-
} else {
|
|
638
|
-
list_.emplace_back();
|
|
639
|
-
handle = &list_.back();
|
|
640
|
-
}
|
|
641
|
-
// Fill handle.
|
|
642
|
-
handle->key = key;
|
|
643
|
-
handle->hash = hash;
|
|
644
|
-
handle->value = value;
|
|
645
|
-
handle->charge = charge;
|
|
646
|
-
handle->meta_charge = meta_charge;
|
|
647
|
-
handle->deleter = deleter;
|
|
648
|
-
uint32_t flags = hold_reference ? kInCacheBit + kOneRef : kInCacheBit;
|
|
649
|
-
|
|
650
|
-
// TODO investigate+fix suspected race condition:
|
|
651
|
-
// [thread 1] Lookup starts, up to Ref()
|
|
652
|
-
// [thread 2] Erase/evict the entry just looked up
|
|
653
|
-
// [thread 1] Ref() the handle, even though it's in the recycle bin
|
|
654
|
-
// [thread 2] Insert with recycling that handle
|
|
655
|
-
// Here we obliterate the other thread's Ref
|
|
656
|
-
// Possible fix: never blindly overwrite the flags, but only make
|
|
657
|
-
// relative updates (fetch_add, etc).
|
|
658
|
-
handle->flags.store(flags, std::memory_order_relaxed);
|
|
659
|
-
HashTable::accessor accessor;
|
|
660
|
-
if (table_.find(accessor, ClockCacheKey(key, hash))) {
|
|
661
|
-
*overwritten = true;
|
|
662
|
-
CacheHandle* existing_handle = accessor->second;
|
|
663
|
-
table_.erase(accessor);
|
|
664
|
-
UnsetInCache(existing_handle, context);
|
|
665
|
-
}
|
|
666
|
-
table_.insert(HashTable::value_type(ClockCacheKey(key, hash), handle));
|
|
667
|
-
if (hold_reference) {
|
|
668
|
-
pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
|
669
|
-
}
|
|
670
|
-
usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
|
671
|
-
return handle;
|
|
320
|
+
strict_capacity_limit_ = strict_capacity_limit;
|
|
672
321
|
}
|
|
673
322
|
|
|
674
323
|
Status ClockCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
|
675
|
-
size_t charge,
|
|
676
|
-
|
|
677
|
-
Cache::
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
324
|
+
size_t charge, Cache::DeleterFn deleter,
|
|
325
|
+
Cache::Handle** handle,
|
|
326
|
+
Cache::Priority priority) {
|
|
327
|
+
if (key.size() != kCacheKeySize) {
|
|
328
|
+
return Status::NotSupported("ClockCache only supports key size " +
|
|
329
|
+
std::to_string(kCacheKeySize) + "B");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
ClockHandle tmp;
|
|
333
|
+
tmp.value = value;
|
|
334
|
+
tmp.deleter = deleter;
|
|
335
|
+
tmp.hash = hash;
|
|
336
|
+
tmp.CalcTotalCharge(charge, metadata_charge_policy_);
|
|
337
|
+
tmp.SetCachePriority(priority);
|
|
338
|
+
for (int i = 0; i < kCacheKeySize; i++) {
|
|
339
|
+
tmp.key_data[i] = key.data()[i];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
Status s = Status::OK();
|
|
343
|
+
autovector<ClockHandle> last_reference_list;
|
|
344
|
+
{
|
|
345
|
+
DMutexLock l(mutex_);
|
|
346
|
+
assert(table_.GetOccupancy() <= table_.GetOccupancyLimit());
|
|
347
|
+
// Free the space following strict clock policy until enough space
|
|
348
|
+
// is freed or the clock list is empty.
|
|
349
|
+
EvictFromClock(tmp.total_charge, &last_reference_list);
|
|
350
|
+
if ((usage_ + tmp.total_charge > capacity_ &&
|
|
351
|
+
(strict_capacity_limit_ || handle == nullptr)) ||
|
|
352
|
+
table_.GetOccupancy() == table_.GetOccupancyLimit()) {
|
|
353
|
+
if (handle == nullptr) {
|
|
354
|
+
// Don't insert the entry but still return ok, as if the entry inserted
|
|
355
|
+
// into cache and get evicted immediately.
|
|
356
|
+
last_reference_list.push_back(tmp);
|
|
357
|
+
} else {
|
|
358
|
+
if (table_.GetOccupancy() == table_.GetOccupancyLimit()) {
|
|
359
|
+
// TODO: Consider using a distinct status for this case, but usually
|
|
360
|
+
// it will be handled the same way as reaching charge capacity limit
|
|
361
|
+
s = Status::MemoryLimit(
|
|
362
|
+
"Insert failed because all slots in the hash table are full.");
|
|
363
|
+
} else {
|
|
364
|
+
s = Status::MemoryLimit(
|
|
365
|
+
"Insert failed because the total charge has exceeded the "
|
|
366
|
+
"capacity.");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
691
369
|
} else {
|
|
692
|
-
|
|
370
|
+
// Insert into the cache. Note that the cache might get larger than its
|
|
371
|
+
// capacity if not enough space was freed up.
|
|
372
|
+
ClockHandle* old;
|
|
373
|
+
ClockHandle* h = table_.Insert(&tmp, &old);
|
|
374
|
+
assert(h != nullptr); // We're below occupancy, so this insertion should
|
|
375
|
+
// never fail.
|
|
376
|
+
usage_ += h->total_charge;
|
|
377
|
+
if (old != nullptr) {
|
|
378
|
+
s = Status::OkOverwritten();
|
|
379
|
+
assert(old->IsVisible());
|
|
380
|
+
table_.Exclude(old);
|
|
381
|
+
if (!old->HasRefs()) {
|
|
382
|
+
// old is in clock because it's in cache and its reference count is 0.
|
|
383
|
+
ClockRemove(old);
|
|
384
|
+
table_.Remove(old);
|
|
385
|
+
assert(usage_ >= old->total_charge);
|
|
386
|
+
usage_ -= old->total_charge;
|
|
387
|
+
last_reference_list.push_back(*old);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (handle == nullptr) {
|
|
391
|
+
ClockInsert(h);
|
|
392
|
+
} else {
|
|
393
|
+
// If caller already holds a ref, no need to take one here.
|
|
394
|
+
if (!h->HasRefs()) {
|
|
395
|
+
h->Ref();
|
|
396
|
+
}
|
|
397
|
+
*handle = reinterpret_cast<Cache::Handle*>(h);
|
|
398
|
+
}
|
|
693
399
|
}
|
|
694
400
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
401
|
+
|
|
402
|
+
// Free the entries here outside of mutex for performance reasons.
|
|
403
|
+
for (auto& h : last_reference_list) {
|
|
404
|
+
h.FreeData();
|
|
698
405
|
}
|
|
699
|
-
|
|
406
|
+
|
|
700
407
|
return s;
|
|
701
408
|
}
|
|
702
409
|
|
|
703
|
-
Cache::Handle* ClockCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (hash != handle->hash || key != handle->key) {
|
|
719
|
-
CleanupContext context;
|
|
720
|
-
Unref(handle, false, &context);
|
|
721
|
-
// It is possible Unref() delete the entry, so we need to cleanup.
|
|
722
|
-
Cleanup(context);
|
|
723
|
-
return nullptr;
|
|
410
|
+
Cache::Handle* ClockCacheShard::Lookup(const Slice& key, uint32_t /* hash */) {
|
|
411
|
+
ClockHandle* h = nullptr;
|
|
412
|
+
{
|
|
413
|
+
DMutexLock l(mutex_);
|
|
414
|
+
h = table_.Lookup(key);
|
|
415
|
+
if (h != nullptr) {
|
|
416
|
+
assert(h->IsVisible());
|
|
417
|
+
if (!h->HasRefs()) {
|
|
418
|
+
// The entry is in clock since it's in the hash table and has no
|
|
419
|
+
// external references.
|
|
420
|
+
ClockRemove(h);
|
|
421
|
+
}
|
|
422
|
+
h->Ref();
|
|
423
|
+
h->SetHit();
|
|
424
|
+
}
|
|
724
425
|
}
|
|
725
|
-
return reinterpret_cast<Cache::Handle*>(
|
|
426
|
+
return reinterpret_cast<Cache::Handle*>(h);
|
|
726
427
|
}
|
|
727
428
|
|
|
728
|
-
bool ClockCacheShard::
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
Cleanup(context);
|
|
736
|
-
return erased;
|
|
429
|
+
bool ClockCacheShard::Ref(Cache::Handle* h) {
|
|
430
|
+
ClockHandle* e = reinterpret_cast<ClockHandle*>(h);
|
|
431
|
+
DMutexLock l(mutex_);
|
|
432
|
+
// To create another reference - entry must be already externally referenced.
|
|
433
|
+
assert(e->HasRefs());
|
|
434
|
+
e->Ref();
|
|
435
|
+
return true;
|
|
737
436
|
}
|
|
738
437
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
438
|
+
bool ClockCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
|
439
|
+
if (handle == nullptr) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
ClockHandle* h = reinterpret_cast<ClockHandle*>(handle);
|
|
443
|
+
ClockHandle copy;
|
|
444
|
+
bool last_reference = false;
|
|
445
|
+
assert(!h->IsInClockList());
|
|
446
|
+
{
|
|
447
|
+
DMutexLock l(mutex_);
|
|
448
|
+
last_reference = h->Unref();
|
|
449
|
+
if (last_reference && h->IsVisible()) {
|
|
450
|
+
// The item is still in cache, and nobody else holds a reference to it.
|
|
451
|
+
if (usage_ > capacity_ || erase_if_last_ref) {
|
|
452
|
+
// The clock list must be empty since the cache is full.
|
|
453
|
+
assert(clock_usage_ == 0 || erase_if_last_ref);
|
|
454
|
+
// Take this opportunity and remove the item.
|
|
455
|
+
table_.Remove(h);
|
|
456
|
+
} else {
|
|
457
|
+
// Put the item back on the clock list, and don't free it.
|
|
458
|
+
ClockInsert(h);
|
|
459
|
+
last_reference = false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// If it was the last reference, then decrement the cache usage.
|
|
463
|
+
if (last_reference) {
|
|
464
|
+
assert(usage_ >= h->total_charge);
|
|
465
|
+
usage_ -= h->total_charge;
|
|
466
|
+
copy = *h;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
744
469
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
HashTable::accessor accessor;
|
|
749
|
-
bool erased = false;
|
|
750
|
-
if (table_.find(accessor, ClockCacheKey(key, hash))) {
|
|
751
|
-
CacheHandle* handle = accessor->second;
|
|
752
|
-
table_.erase(accessor);
|
|
753
|
-
erased = UnsetInCache(handle, context);
|
|
470
|
+
// Free the entry here outside of mutex for performance reasons.
|
|
471
|
+
if (last_reference) {
|
|
472
|
+
copy.FreeData();
|
|
754
473
|
}
|
|
755
|
-
return
|
|
474
|
+
return last_reference;
|
|
756
475
|
}
|
|
757
476
|
|
|
758
|
-
void ClockCacheShard::
|
|
759
|
-
|
|
477
|
+
void ClockCacheShard::Erase(const Slice& key, uint32_t /* hash */) {
|
|
478
|
+
ClockHandle copy;
|
|
479
|
+
bool last_reference = false;
|
|
760
480
|
{
|
|
761
481
|
DMutexLock l(mutex_);
|
|
762
|
-
table_.
|
|
763
|
-
|
|
764
|
-
|
|
482
|
+
ClockHandle* h = table_.Lookup(key);
|
|
483
|
+
if (h != nullptr) {
|
|
484
|
+
table_.Exclude(h);
|
|
485
|
+
if (!h->HasRefs()) {
|
|
486
|
+
// The entry is in Clock since it's in cache and has no external
|
|
487
|
+
// references.
|
|
488
|
+
ClockRemove(h);
|
|
489
|
+
table_.Remove(h);
|
|
490
|
+
assert(usage_ >= h->total_charge);
|
|
491
|
+
usage_ -= h->total_charge;
|
|
492
|
+
last_reference = true;
|
|
493
|
+
copy = *h;
|
|
494
|
+
}
|
|
765
495
|
}
|
|
766
496
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
public:
|
|
772
|
-
ClockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
|
773
|
-
CacheMetadataChargePolicy metadata_charge_policy)
|
|
774
|
-
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
|
|
775
|
-
int num_shards = 1 << num_shard_bits;
|
|
776
|
-
shards_ = new ClockCacheShard[num_shards];
|
|
777
|
-
for (int i = 0; i < num_shards; i++) {
|
|
778
|
-
shards_[i].set_metadata_charge_policy(metadata_charge_policy);
|
|
779
|
-
}
|
|
780
|
-
SetCapacity(capacity);
|
|
781
|
-
SetStrictCapacityLimit(strict_capacity_limit);
|
|
497
|
+
// Free the entry here outside of mutex for performance reasons.
|
|
498
|
+
// last_reference will only be true if e != nullptr.
|
|
499
|
+
if (last_reference) {
|
|
500
|
+
copy.FreeData();
|
|
782
501
|
}
|
|
502
|
+
}
|
|
783
503
|
|
|
784
|
-
|
|
504
|
+
size_t ClockCacheShard::GetUsage() const {
|
|
505
|
+
DMutexLock l(mutex_);
|
|
506
|
+
return usage_;
|
|
507
|
+
}
|
|
785
508
|
|
|
786
|
-
|
|
509
|
+
size_t ClockCacheShard::GetPinnedUsage() const {
|
|
510
|
+
DMutexLock l(mutex_);
|
|
511
|
+
assert(usage_ >= clock_usage_);
|
|
512
|
+
return usage_ - clock_usage_;
|
|
513
|
+
}
|
|
787
514
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
515
|
+
std::string ClockCacheShard::GetPrintableOptions() const {
|
|
516
|
+
return std::string{};
|
|
517
|
+
}
|
|
791
518
|
|
|
792
|
-
|
|
793
|
-
|
|
519
|
+
ClockCache::ClockCache(size_t capacity, size_t estimated_value_size,
|
|
520
|
+
int num_shard_bits, bool strict_capacity_limit,
|
|
521
|
+
CacheMetadataChargePolicy metadata_charge_policy)
|
|
522
|
+
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
|
|
523
|
+
assert(estimated_value_size > 0 ||
|
|
524
|
+
metadata_charge_policy != kDontChargeCacheMetadata);
|
|
525
|
+
num_shards_ = 1 << num_shard_bits;
|
|
526
|
+
shards_ = reinterpret_cast<ClockCacheShard*>(
|
|
527
|
+
port::cacheline_aligned_alloc(sizeof(ClockCacheShard) * num_shards_));
|
|
528
|
+
size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
|
|
529
|
+
for (int i = 0; i < num_shards_; i++) {
|
|
530
|
+
new (&shards_[i])
|
|
531
|
+
ClockCacheShard(per_shard, estimated_value_size, strict_capacity_limit,
|
|
532
|
+
metadata_charge_policy);
|
|
794
533
|
}
|
|
534
|
+
}
|
|
795
535
|
|
|
796
|
-
|
|
797
|
-
|
|
536
|
+
ClockCache::~ClockCache() {
|
|
537
|
+
if (shards_ != nullptr) {
|
|
538
|
+
assert(num_shards_ > 0);
|
|
539
|
+
for (int i = 0; i < num_shards_; i++) {
|
|
540
|
+
shards_[i].~ClockCacheShard();
|
|
541
|
+
}
|
|
542
|
+
port::cacheline_aligned_free(shards_);
|
|
798
543
|
}
|
|
544
|
+
}
|
|
799
545
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
546
|
+
CacheShard* ClockCache::GetShard(uint32_t shard) {
|
|
547
|
+
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
|
548
|
+
}
|
|
803
549
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
550
|
+
const CacheShard* ClockCache::GetShard(uint32_t shard) const {
|
|
551
|
+
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
|
552
|
+
}
|
|
807
553
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
554
|
+
void* ClockCache::Value(Handle* handle) {
|
|
555
|
+
return reinterpret_cast<const ClockHandle*>(handle)->value;
|
|
556
|
+
}
|
|
811
557
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
558
|
+
size_t ClockCache::GetCharge(Handle* handle) const {
|
|
559
|
+
CacheMetadataChargePolicy metadata_charge_policy = kDontChargeCacheMetadata;
|
|
560
|
+
if (num_shards_ > 0) {
|
|
561
|
+
metadata_charge_policy = shards_[0].metadata_charge_policy_;
|
|
817
562
|
}
|
|
563
|
+
return reinterpret_cast<const ClockHandle*>(handle)->GetCharge(
|
|
564
|
+
metadata_charge_policy);
|
|
565
|
+
}
|
|
818
566
|
|
|
819
|
-
|
|
567
|
+
Cache::DeleterFn ClockCache::GetDeleter(Handle* handle) const {
|
|
568
|
+
auto h = reinterpret_cast<const ClockHandle*>(handle);
|
|
569
|
+
return h->deleter;
|
|
570
|
+
}
|
|
820
571
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
572
|
+
uint32_t ClockCache::GetHash(Handle* handle) const {
|
|
573
|
+
return reinterpret_cast<const ClockHandle*>(handle)->hash;
|
|
574
|
+
}
|
|
824
575
|
|
|
825
|
-
|
|
576
|
+
void ClockCache::DisownData() {
|
|
577
|
+
// Leak data only if that won't generate an ASAN/valgrind warning.
|
|
578
|
+
if (!kMustFreeHeapAllocations) {
|
|
579
|
+
shards_ = nullptr;
|
|
580
|
+
num_shards_ = 0;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
} // namespace clock_cache
|
|
826
585
|
|
|
827
586
|
std::shared_ptr<Cache> NewClockCache(
|
|
828
587
|
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
|
829
588
|
CacheMetadataChargePolicy metadata_charge_policy) {
|
|
589
|
+
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit, 0.5,
|
|
590
|
+
nullptr, kDefaultToAdaptiveMutex, metadata_charge_policy);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
std::shared_ptr<Cache> ExperimentalNewClockCache(
|
|
594
|
+
size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
|
595
|
+
bool strict_capacity_limit,
|
|
596
|
+
CacheMetadataChargePolicy metadata_charge_policy) {
|
|
597
|
+
if (num_shard_bits >= 20) {
|
|
598
|
+
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
|
599
|
+
}
|
|
830
600
|
if (num_shard_bits < 0) {
|
|
831
601
|
num_shard_bits = GetDefaultCacheShardBits(capacity);
|
|
832
602
|
}
|
|
833
|
-
return std::make_shared<ClockCache>(
|
|
834
|
-
capacity, num_shard_bits, strict_capacity_limit,
|
|
603
|
+
return std::make_shared<clock_cache::ClockCache>(
|
|
604
|
+
capacity, estimated_value_size, num_shard_bits, strict_capacity_limit,
|
|
605
|
+
metadata_charge_policy);
|
|
835
606
|
}
|
|
836
607
|
|
|
837
608
|
} // namespace ROCKSDB_NAMESPACE
|
|
838
|
-
|
|
839
|
-
#endif // SUPPORT_CLOCK_CACHE
|