Bitcoin ABC 0.33.12
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
10#include <algorithm> // std::find
11#include <array>
12#include <atomic>
13#include <cmath>
14#include <cstring>
15#include <limits>
16#include <memory>
17#include <utility>
18#include <vector>
19
32namespace CuckooCache {
47 std::unique_ptr<std::atomic<uint8_t>[]> mem;
48
49public:
52
64 explicit bit_packed_atomic_flags(uint32_t size) {
65 // pad out the size if needed
66 size = (size + 7) / 8;
67 mem.reset(new std::atomic<uint8_t>[size]);
68 for (uint32_t i = 0; i < size; ++i) {
69 mem[i].store(0xFF);
70 }
71 };
72
83 inline void setup(uint32_t b) {
85 std::swap(mem, d.mem);
86 }
87
95 inline void bit_set(uint32_t s) {
96 mem[s >> 3].fetch_or(uint8_t(1 << (s & 7)), std::memory_order_relaxed);
97 }
98
106 inline void bit_unset(uint32_t s) {
107 mem[s >> 3].fetch_and(uint8_t(~(1 << (s & 7))),
108 std::memory_order_relaxed);
109 }
110
117 inline bool bit_is_set(uint32_t s) const {
118 return (1 << (s & 7)) & mem[s >> 3].load(std::memory_order_relaxed);
119 }
120};
121
166template <typename Element, typename Hash> class cache {
167private:
169 std::vector<Element> table;
170
172 uint32_t size{0};
173
179
185 mutable std::vector<bool> epoch_flags;
186
194
204 uint32_t epoch_size{0};
205
210 uint8_t depth_limit{0};
211
218
222 using Key = typename Element::KeyType;
223
261 inline std::array<uint32_t, 8> compute_hashes(const Key &k) const {
262 return {
263 {FastRange32(hash_function.template operator()<0>(k), size),
264 FastRange32(hash_function.template operator()<1>(k), size),
265 FastRange32(hash_function.template operator()<2>(k), size),
266 FastRange32(hash_function.template operator()<3>(k), size),
267 FastRange32(hash_function.template operator()<4>(k), size),
268 FastRange32(hash_function.template operator()<5>(k), size),
269 FastRange32(hash_function.template operator()<6>(k), size),
270 FastRange32(hash_function.template operator()<7>(k), size)}
271 };
272 }
273
278 constexpr uint32_t invalid() const { return ~uint32_t(0); }
279
285 inline void allow_erase(uint32_t n) const { collection_flags.bit_set(n); }
286
292 inline void please_keep(uint32_t n) const { collection_flags.bit_unset(n); }
293
304 void epoch_check() {
305 if (epoch_heuristic_counter != 0) {
307 return;
308 }
309 // count the number of elements from the latest epoch which have not
310 // been erased.
311 uint32_t epoch_unused_count = 0;
312 for (uint32_t i = 0; i < size; ++i) {
313 epoch_unused_count +=
315 }
316 // If there are more non-deleted entries in the current epoch than the
317 // epoch size, then allow_erase on all elements in the old epoch (marked
318 // false) and move all elements in the current epoch to the old epoch
319 // but do not call allow_erase on their indices.
320 if (epoch_unused_count >= epoch_size) {
321 for (uint32_t i = 0; i < size; ++i) {
322 if (epoch_flags[i]) {
323 epoch_flags[i] = false;
324 } else {
325 allow_erase(i);
326 }
327 }
329 } else {
330 // reset the epoch_heuristic_counter to next do a scan when worst
331 // case behavior (no intermittent erases) would exceed epoch size,
332 // with a reasonable minimum scan size. Ordinarily, we would have to
333 // sanity check std::min(epoch_size, epoch_unused_count), but we
334 // already know that `epoch_unused_count < epoch_size` in this
335 // branch
336 epoch_heuristic_counter = std::max(
337 1u, std::max(epoch_size / 16, epoch_size - epoch_unused_count));
338 }
339 }
340
341public:
347
357 uint32_t setup(uint32_t new_size) {
358 // depth_limit must be at least one otherwise errors can occur.
359 size = std::max<uint32_t>(2, new_size);
360 depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(size)));
361 table.resize(size);
363 epoch_flags.resize(size);
364 // Set to 45% as described above
365 epoch_size = std::max((uint32_t)1, (45 * size) / 100);
366 // Initially set to wait for a whole epoch
368 return size;
369 }
370
385 std::pair<uint32_t, size_t> setup_bytes(size_t bytes) {
386 uint32_t requested_num_elems(std::min<size_t>(
387 bytes / sizeof(Element), std::numeric_limits<uint32_t>::max()));
388
389 auto num_elems = setup(requested_num_elems);
390
391 size_t approx_size_bytes = num_elems * sizeof(Element);
392 return std::make_pair(num_elems, approx_size_bytes);
393 }
394
420 inline void insert(Element e, bool replace = false) {
421 epoch_check();
422 uint32_t last_loc = invalid();
423 bool last_epoch = true;
424 std::array<uint32_t, 8> locs = compute_hashes(e.getKey());
425 // Make sure we have not already inserted this element.
426 // If we have, make sure that it does not get deleted.
427 for (const uint32_t loc : locs) {
428 if (table[loc].getKey() == e.getKey()) {
429 if (replace) {
430 table[loc] = std::move(e);
431 }
432 please_keep(loc);
433 epoch_flags[loc] = last_epoch;
434 return;
435 }
436 }
437 for (uint8_t depth = 0; depth < depth_limit; ++depth) {
438 // First try to insert to an empty slot, if one exists
439 for (const uint32_t loc : locs) {
440 if (!collection_flags.bit_is_set(loc)) {
441 continue;
442 }
443 table[loc] = std::move(e);
444 please_keep(loc);
445 epoch_flags[loc] = last_epoch;
446 return;
447 }
463 last_loc =
464 locs[(1 + (std::find(locs.begin(), locs.end(), last_loc) -
465 locs.begin())) &
466 7];
467 std::swap(table[last_loc], e);
468 // Can't std::swap a std::vector<bool>::reference and a bool&.
469 bool epoch = last_epoch;
470 last_epoch = epoch_flags[last_loc];
471 epoch_flags[last_loc] = epoch;
472
473 // Recompute the locs -- unfortunately happens one too many times!
474 locs = compute_hashes(e.getKey());
475 }
476 }
477
506 bool contains(const Key &k, const bool erase) const {
507 return find(k, erase) != nullptr;
508 }
509
522 bool get(Element &e, const bool erase) const {
523 if (const Element *eptr = find(e.getKey(), erase)) {
524 e = *eptr;
525 return true;
526 }
527
528 return false;
529 }
530
531private:
532 const Element *find(const Key &k, const bool erase) const {
533 std::array<uint32_t, 8> locs = compute_hashes(k);
534 for (const uint32_t loc : locs) {
535 if (table[loc].getKey() == k) {
536 if (erase) {
537 allow_erase(loc);
538 }
539 return &table[loc];
540 }
541 }
542 return nullptr;
543 }
544};
545
549template <typename T> struct KeyOnly : public T {
550 // For contains.
551 using KeyType = T;
552
553 // Ensure implicit conversion from T.
554 KeyOnly() = default;
555 KeyOnly(const T &x) : T(x) {}
556
557 // Implement required features.
558 const T &getKey() const { return *this; }
559};
560
561} // namespace CuckooCache
562
563#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:95
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:83
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:117
void bit_unset(uint32_t s)
bit_unset marks an entry as something that should not be overwritten.
Definition: cuckoocache.h:106
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:166
uint32_t size
size stores the total available slots in the hash table
Definition: cuckoocache.h:172
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:385
uint8_t depth_limit
depth_limit determines how many elements insert should try to replace.
Definition: cuckoocache.h:210
std::vector< Element > table
table stores all the elements
Definition: cuckoocache.h:169
typename Element::KeyType Key
Key is the key type for this map or set.
Definition: cuckoocache.h:222
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:193
std::array< uint32_t, 8 > compute_hashes(const Key &k) const
compute_hashes is convenience for not having to write out this expression everywhere we use the hash ...
Definition: cuckoocache.h:261
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:357
void epoch_check()
epoch_check handles the changing of epochs for elements stored in the cache.
Definition: cuckoocache.h:304
bool get(Element &e, const bool erase) const
get is almost identical to contains(), with the difference that it obtains the found element (for Ele...
Definition: cuckoocache.h:522
uint32_t epoch_size
epoch_size is set to be the number of elements supposed to be in a epoch.
Definition: cuckoocache.h:204
std::vector< bool > epoch_flags
epoch_flags tracks how recently an element was inserted into the cache.
Definition: cuckoocache.h:185
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:178
const Element * find(const Key &k, const bool erase) const
Definition: cuckoocache.h:532
bool contains(const Key &k, 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:506
constexpr uint32_t invalid() const
invalid returns a special index that can never be inserted to
Definition: cuckoocache.h:278
const Hash hash_function
hash_function is a const instance of the hash function.
Definition: cuckoocache.h:217
void allow_erase(uint32_t n) const
allow_erase marks the element at index n as discardable.
Definition: cuckoocache.h:285
void insert(Element e, bool replace=false)
insert loops at most depth_limit times trying to insert a hash at various locations in the table via ...
Definition: cuckoocache.h:420
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:292
cache()
You must always construct a cache with some elements via a subsequent call to setup or setup_bytes,...
Definition: cuckoocache.h:346
static uint32_t FastRange32(uint32_t x, uint32_t n)
This file offers implementations of the fast range reduction technique described in https://lemire....
Definition: fastrange.h:22
uint256 Hash(const T &in1)
Compute the 256-bit hash of an object.
Definition: hash.h:74
High-performance cache primitives.
Definition: cuckoocache.h:32
Helper class used when we only want the cache to be a set rather than a map.
Definition: cuckoocache.h:549
KeyOnly(const T &x)
Definition: cuckoocache.h:555
const T & getKey() const
Definition: cuckoocache.h:558