Bitcoin Core  0.19.99
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 <array>
9 #include <atomic>
10 #include <cmath>
11 #include <cstring>
12 #include <memory>
13 #include <utility>
14 #include <vector>
15 
16 
26 namespace CuckooCache
27 {
41 {
42  std::unique_ptr<std::atomic<uint8_t>[]> mem;
43 
44 public:
46  bit_packed_atomic_flags() = delete;
47 
59  explicit bit_packed_atomic_flags(uint32_t size)
60  {
61  // pad out the size if needed
62  size = (size + 7) / 8;
63  mem.reset(new std::atomic<uint8_t>[size]);
64  for (uint32_t i = 0; i < size; ++i)
65  mem[i].store(0xFF);
66  };
67 
77  inline void setup(uint32_t b)
78  {
80  std::swap(mem, d.mem);
81  }
82 
89  inline void bit_set(uint32_t s)
90  {
91  mem[s >> 3].fetch_or(1 << (s & 7), std::memory_order_relaxed);
92  }
93 
100  inline void bit_unset(uint32_t s)
101  {
102  mem[s >> 3].fetch_and(~(1 << (s & 7)), std::memory_order_relaxed);
103  }
104 
110  inline bool bit_is_set(uint32_t s) const
111  {
112  return (1 << (s & 7)) & mem[s >> 3].load(std::memory_order_relaxed);
113  }
114 };
115 
156 template <typename Element, typename Hash>
157 class cache
158 {
159 private:
161  std::vector<Element> table;
162 
164  uint32_t size;
165 
169 
174  mutable std::vector<bool> epoch_flags;
175 
182 
191  uint32_t epoch_size;
192 
196  uint8_t depth_limit;
197 
203 
241  inline std::array<uint32_t, 8> compute_hashes(const Element& e) const
242  {
243  return {{(uint32_t)(((uint64_t)hash_function.template operator()<0>(e) * (uint64_t)size) >> 32),
244  (uint32_t)(((uint64_t)hash_function.template operator()<1>(e) * (uint64_t)size) >> 32),
245  (uint32_t)(((uint64_t)hash_function.template operator()<2>(e) * (uint64_t)size) >> 32),
246  (uint32_t)(((uint64_t)hash_function.template operator()<3>(e) * (uint64_t)size) >> 32),
247  (uint32_t)(((uint64_t)hash_function.template operator()<4>(e) * (uint64_t)size) >> 32),
248  (uint32_t)(((uint64_t)hash_function.template operator()<5>(e) * (uint64_t)size) >> 32),
249  (uint32_t)(((uint64_t)hash_function.template operator()<6>(e) * (uint64_t)size) >> 32),
250  (uint32_t)(((uint64_t)hash_function.template operator()<7>(e) * (uint64_t)size) >> 32)}};
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  {
266  collection_flags.bit_set(n);
267  }
268 
273  inline void please_keep(uint32_t n) const
274  {
275  collection_flags.bit_unset(n);
276  }
277 
287  void epoch_check()
288  {
289  if (epoch_heuristic_counter != 0) {
290  --epoch_heuristic_counter;
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] &&
298  !collection_flags.bit_is_set(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);
309  epoch_heuristic_counter = epoch_size;
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 
321 public:
325  cache() : table(), size(), collection_flags(0), epoch_flags(),
326  epoch_heuristic_counter(), epoch_size(), depth_limit(0), hash_function()
327  {
328  }
329 
338  uint32_t setup(uint32_t new_size)
339  {
340  // depth_limit must be at least one otherwise errors can occur.
341  depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(std::max((uint32_t)2, new_size))));
342  size = std::max<uint32_t>(2, new_size);
343  table.resize(size);
344  collection_flags.setup(size);
345  epoch_flags.resize(size);
346  // Set to 45% as described above
347  epoch_size = std::max((uint32_t)1, (45 * size) / 100);
348  // Initially set to wait for a whole epoch
349  epoch_heuristic_counter = epoch_size;
350  return size;
351  }
352 
365  uint32_t setup_bytes(size_t bytes)
366  {
367  return setup(bytes/sizeof(Element));
368  }
369 
391  inline void insert(Element e)
392  {
393  epoch_check();
394  uint32_t last_loc = invalid();
395  bool last_epoch = true;
396  std::array<uint32_t, 8> locs = compute_hashes(e);
397  // Make sure we have not already inserted this element
398  // If we have, make sure that it does not get deleted
399  for (const uint32_t loc : locs)
400  if (table[loc] == e) {
401  please_keep(loc);
402  epoch_flags[loc] = last_epoch;
403  return;
404  }
405  for (uint8_t depth = 0; depth < depth_limit; ++depth) {
406  // First try to insert to an empty slot, if one exists
407  for (const uint32_t loc : locs) {
408  if (!collection_flags.bit_is_set(loc))
409  continue;
410  table[loc] = std::move(e);
411  please_keep(loc);
412  epoch_flags[loc] = last_epoch;
413  return;
414  }
429  last_loc = locs[(1 + (std::find(locs.begin(), locs.end(), last_loc) - locs.begin())) & 7];
430  std::swap(table[last_loc], e);
431  // Can't std::swap a std::vector<bool>::reference and a bool&.
432  bool epoch = last_epoch;
433  last_epoch = epoch_flags[last_loc];
434  epoch_flags[last_loc] = epoch;
435 
436  // Recompute the locs -- unfortunately happens one too many times!
437  locs = compute_hashes(e);
438  }
439  }
440 
468  inline bool contains(const Element& e, const bool erase) const
469  {
470  std::array<uint32_t, 8> locs = compute_hashes(e);
471  for (const uint32_t loc : locs)
472  if (table[loc] == e) {
473  if (erase)
474  allow_erase(loc);
475  return true;
476  }
477  return false;
478  }
479 };
480 } // namespace CuckooCache
481 
482 #endif // BITCOIN_CUCKOOCACHE_H
bool bit_is_set(uint32_t s) const
bit_is_set queries the table for discardability at s.
Definition: cuckoocache.h:110
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
std::unique_ptr< std::atomic< uint8_t >[]> mem
Definition: cuckoocache.h:42
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:59
cache implements a cache with properties similar to a cuckoo-set.
Definition: cuckoocache.h:157
void bit_set(uint32_t s)
bit_set sets an entry as discardable.
Definition: cuckoocache.h:89
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:468
uint32_t epoch_size
epoch_size is set to be the number of elements supposed to be in a epoch.
Definition: cuckoocache.h:191
std::vector< bool > epoch_flags
epoch_flags tracks how recently an element was inserted into the cache.
Definition: cuckoocache.h:174
const Hash hash_function
hash_function is a const instance of the hash function.
Definition: cuckoocache.h:202
bit_packed_atomic_flags implements a container for garbage collection flags that is only thread unsaf...
Definition: cuckoocache.h:40
cache()
You must always construct a cache with some elements via a subsequent call to setup or setup_bytes...
Definition: cuckoocache.h:325
uint32_t size
size stores the total available slots in the hash table
Definition: cuckoocache.h:164
uint8_t depth_limit
depth_limit determines how many elements insert should try to replace.
Definition: cuckoocache.h:196
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:181
uint256 Hash(const T1 pbegin, const T1 pend)
Compute the 256-bit hash of an object.
Definition: hash.h:71
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:391
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 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
std::vector< Element > table
table stores all the elements
Definition: cuckoocache.h:161
High-performance cache primitives.
Definition: cuckoocache.h:26
void epoch_check()
epoch_check handles the changing of epochs for elements stored in the cache.
Definition: cuckoocache.h:287
void allow_erase(uint32_t n) const
allow_erase marks the element at index n as discardable.
Definition: cuckoocache.h:264
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:168
uint32_t setup(uint32_t new_size)
setup initializes the container to store no more than new_size elements.
Definition: cuckoocache.h:338
void bit_unset(uint32_t s)
bit_unset marks an entry as something that should not be overwritten.
Definition: cuckoocache.h:100
constexpr uint32_t invalid() const
invalid returns a special index that can never be inserted to
Definition: cuckoocache.h:255
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:77
bit_packed_atomic_flags()=delete
No default constructor, as there must be some size.