Bitcoin Core 31.99.0
P2P Digital Currency
cuckoocache.h
Go to the documentation of this file.
1// Copyright (c) 2016 Jeremy Rubin
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#ifndef BITCOIN_CUCKOOCACHE_H
6#define BITCOIN_CUCKOOCACHE_H
7
8#include <util/fastrange.h>
9#include <util/overflow.h>
10
11#include <algorithm>
12#include <array>
13#include <atomic>
14#include <cmath>
15#include <cstring>
16#include <limits>
17#include <memory>
18#include <utility>
19#include <vector>
20
21
31namespace CuckooCache
32{
46{
47 std::unique_ptr<std::atomic<uint8_t>[]> mem;
48
49public:
52
64 explicit bit_packed_atomic_flags(uint32_t size)
65 {
66 // pad out the size if needed
67 size = CeilDiv(size, 8u);
68 mem.reset(new std::atomic<uint8_t>[size]);
69 for (uint32_t i = 0; i < size; ++i)
70 mem[i].store(0xFF);
71 };
72
82 inline void setup(uint32_t b)
83 {
85 std::swap(mem, d.mem);
86 }
87
94 inline void bit_set(uint32_t s)
95 {
96 mem[s >> 3].fetch_or(uint8_t(1 << (s & 7)), std::memory_order_relaxed);
97 }
98
105 inline void bit_unset(uint32_t s)
106 {
107 mem[s >> 3].fetch_and(uint8_t(~(1 << (s & 7))), std::memory_order_relaxed);
108 }
109
115 inline bool bit_is_set(uint32_t s) const
116 {
117 return (1 << (s & 7)) & mem[s >> 3].load(std::memory_order_relaxed);
118 }
119};
120
161template <typename Element, typename Hash>
162class cache
163{
164private:
166 std::vector<Element> table;
167
169 uint32_t size{0};
170
174
179 mutable std::vector<bool> epoch_flags;
180
187
196 uint32_t epoch_size{0};
197
201 uint8_t depth_limit{0};
202
208
241 inline std::array<uint32_t, 8> compute_hashes(const Element& e) const
242 {
243 return {{FastRange32(hash_function.template operator()<0>(e), size),
244 FastRange32(hash_function.template operator()<1>(e), size),
245 FastRange32(hash_function.template operator()<2>(e), size),
246 FastRange32(hash_function.template operator()<3>(e), size),
247 FastRange32(hash_function.template operator()<4>(e), size),
248 FastRange32(hash_function.template operator()<5>(e), size),
249 FastRange32(hash_function.template operator()<6>(e), size),
250 FastRange32(hash_function.template operator()<7>(e), size)}};
251 }
252
255 constexpr uint32_t invalid() const
256 {
257 return ~(uint32_t)0;
258 }
259
264 inline void allow_erase(uint32_t n) const
265 {
267 }
268
273 inline void please_keep(uint32_t n) const
274 {
276 }
277
288 {
289 if (epoch_heuristic_counter != 0) {
291 return;
292 }
293 // count the number of elements from the latest epoch which
294 // have not been erased.
295 uint32_t epoch_unused_count = 0;
296 for (uint32_t i = 0; i < size; ++i)
297 epoch_unused_count += epoch_flags[i] &&
299 // If there are more non-deleted entries in the current epoch than the
300 // epoch size, then allow_erase on all elements in the old epoch (marked
301 // false) and move all elements in the current epoch to the old epoch
302 // but do not call allow_erase on their indices.
303 if (epoch_unused_count >= epoch_size) {
304 for (uint32_t i = 0; i < size; ++i)
305 if (epoch_flags[i])
306 epoch_flags[i] = false;
307 else
308 allow_erase(i);
310 } else
311 // reset the epoch_heuristic_counter to next do a scan when worst
312 // case behavior (no intermittent erases) would exceed epoch size,
313 // with a reasonable minimum scan size.
314 // Ordinarily, we would have to sanity check std::min(epoch_size,
315 // epoch_unused_count), but we already know that `epoch_unused_count
316 // < epoch_size` in this branch
317 epoch_heuristic_counter = std::max(1u, std::max(epoch_size / 16,
318 epoch_size - epoch_unused_count));
319 }
320
321public:
326 {
327 }
328
337 uint32_t setup(uint32_t new_size)
338 {
339 // depth_limit must be at least one otherwise errors can occur.
340 size = std::max<uint32_t>(2, new_size);
341 depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(size)));
342 table.resize(size);
344 epoch_flags.resize(size);
345 // Set to 45% as described above
346 epoch_size = std::max(uint32_t{1}, (45 * size) / 100);
347 // Initially set to wait for a whole epoch
349 return size;
350 }
351
365 std::pair<uint32_t, size_t> setup_bytes(size_t bytes)
366 {
367 uint32_t requested_num_elems(std::min<size_t>(
368 bytes / sizeof(Element),
369 std::numeric_limits<uint32_t>::max()));
370
371 auto num_elems = setup(requested_num_elems);
372
373 size_t approx_size_bytes = num_elems * sizeof(Element);
374 return std::make_pair(num_elems, approx_size_bytes);
375 }
376
398 inline void insert(Element e)
399 {
400 epoch_check();
401 uint32_t last_loc = invalid();
402 bool last_epoch = true;
403 std::array<uint32_t, 8> locs = compute_hashes(e);
404 // Make sure we have not already inserted this element
405 // If we have, make sure that it does not get deleted
406 for (const uint32_t loc : locs)
407 if (table[loc] == e) {
408 please_keep(loc);
409 epoch_flags[loc] = last_epoch;
410 return;
411 }
412 for (uint8_t depth = 0; depth < depth_limit; ++depth) {
413 // First try to insert to an empty slot, if one exists
414 for (const uint32_t loc : locs) {
416 continue;
417 table[loc] = std::move(e);
418 please_keep(loc);
419 epoch_flags[loc] = last_epoch;
420 return;
421 }
436 last_loc = locs[(1 + (std::find(locs.begin(), locs.end(), last_loc) - locs.begin())) & 7];
437 std::swap(table[last_loc], e);
438 // Can't std::swap a std::vector<bool>::reference and a bool&.
439 bool epoch = last_epoch;
440 last_epoch = epoch_flags[last_loc];
441 epoch_flags[last_loc] = epoch;
442
443 // Recompute the locs -- unfortunately happens one too many times!
444 locs = compute_hashes(e);
445 }
446 }
447
475 inline bool contains(const Element& e, const bool erase) const
476 {
477 std::array<uint32_t, 8> locs = compute_hashes(e);
478 for (const uint32_t loc : locs)
479 if (table[loc] == e) {
480 if (erase)
481 allow_erase(loc);
482 return true;
483 }
484 return false;
485 }
486};
487} // namespace CuckooCache
488
489#endif // BITCOIN_CUCKOOCACHE_H
bit_packed_atomic_flags implements a container for garbage collection flags that is only thread unsaf...
Definition: cuckoocache.h:46
void bit_set(uint32_t s)
bit_set sets an entry as discardable.
Definition: cuckoocache.h:94
void setup(uint32_t b)
setup marks all entries and ensures that bit_packed_atomic_flags can store at least b entries.
Definition: cuckoocache.h:82
bit_packed_atomic_flags()=delete
No default constructor, as there must be some size.
bool bit_is_set(uint32_t s) const
bit_is_set queries the table for discardability at s.
Definition: cuckoocache.h:115
void bit_unset(uint32_t s)
bit_unset marks an entry as something that should not be overwritten.
Definition: cuckoocache.h:105
bit_packed_atomic_flags(uint32_t size)
bit_packed_atomic_flags constructor creates memory to sufficiently keep track of garbage collection i...
Definition: cuckoocache.h:64
std::unique_ptr< std::atomic< uint8_t >[]> mem
Definition: cuckoocache.h:47
cache implements a cache with properties similar to a cuckoo-set.
Definition: cuckoocache.h:163
uint32_t size
size stores the total available slots in the hash table
Definition: cuckoocache.h:169
std::pair< uint32_t, size_t > setup_bytes(size_t bytes)
setup_bytes is a convenience function which accounts for internal memory usage when deciding how many...
Definition: cuckoocache.h:365
uint8_t depth_limit
depth_limit determines how many elements insert should try to replace.
Definition: cuckoocache.h:201
std::vector< Element > table
table stores all the elements
Definition: cuckoocache.h:166
uint32_t epoch_heuristic_counter
epoch_heuristic_counter is used to determine when an epoch might be aged & an expensive scan should b...
Definition: cuckoocache.h:186
uint32_t setup(uint32_t new_size)
setup initializes the container to store no more than new_size elements and no less than 2 elements.
Definition: cuckoocache.h:337
void epoch_check()
epoch_check handles the changing of epochs for elements stored in the cache.
Definition: cuckoocache.h:287
std::array< uint32_t, 8 > compute_hashes(const Element &e) const
compute_hashes is convenience for not having to write out this expression everywhere we use the hash ...
Definition: cuckoocache.h:241
uint32_t epoch_size
epoch_size is set to be the number of elements supposed to be in a epoch.
Definition: cuckoocache.h:196
std::vector< bool > epoch_flags
epoch_flags tracks how recently an element was inserted into the cache.
Definition: cuckoocache.h:179
void insert(Element e)
insert loops at most depth_limit times trying to insert a hash at various locations in the table via ...
Definition: cuckoocache.h:398
bit_packed_atomic_flags collection_flags
The bit_packed_atomic_flags array is marked mutable because we want garbage collection to be allowed ...
Definition: cuckoocache.h:173
constexpr uint32_t invalid() const
invalid returns a special index that can never be inserted to
Definition: cuckoocache.h:255
const Hash hash_function
hash_function is a const instance of the hash function.
Definition: cuckoocache.h:207
void allow_erase(uint32_t n) const
allow_erase marks the element at index n as discardable.
Definition: cuckoocache.h:264
bool contains(const Element &e, const bool erase) const
contains iterates through the hash locations for a given element and checks to see if it is present.
Definition: cuckoocache.h:475
void please_keep(uint32_t n) const
please_keep marks the element at index n as an entry that should be kept.
Definition: cuckoocache.h:273
cache()
You must always construct a cache with some elements via a subsequent call to setup or setup_bytes,...
Definition: cuckoocache.h:325
static uint32_t FastRange32(uint32_t x, uint32_t n)
Fast range reduction with 32-bit input and 32-bit range.
Definition: fastrange.h:19
uint256 Hash(const T &in1)
Compute the 256-bit hash of an object.
Definition: hash.h:75
High-performance cache primitives.
Definition: cuckoocache.h:32
constexpr auto CeilDiv(const Dividend dividend, const Divisor divisor)
Integer ceiling division (for unsigned values).
Definition: overflow.h:70