libcuckoo  0.3.1
bucket_container.hh
1 #ifndef BUCKET_CONTAINER_H
2 #define BUCKET_CONTAINER_H
3 
4 #include <array>
5 #include <atomic>
6 #include <cassert>
7 #include <cstddef>
8 #include <iostream>
9 #include <memory>
10 #include <type_traits>
11 #include <utility>
12 
13 #include "cuckoohash_util.hh"
14 
15 namespace libcuckoo {
16 
29 template <class Key, class T, class Allocator, class Partial,
30  std::size_t SLOT_PER_BUCKET>
32 public:
33  using key_type = Key;
34  using mapped_type = T;
35  using value_type = std::pair<const Key, T>;
36 
37 private:
38  using traits_ = typename std::allocator_traits<
39  Allocator>::template rebind_traits<value_type>;
40 
41 public:
42  using allocator_type = typename traits_::allocator_type;
43  using partial_t = Partial;
44  using size_type = typename traits_::size_type;
45  using reference = value_type &;
46  using const_reference = const value_type &;
47  using pointer = typename traits_::pointer;
48  using const_pointer = typename traits_::const_pointer;
49 
50  /*
51  * The bucket type holds SLOT_PER_BUCKET key-value pairs, along with their
52  * partial keys and occupancy info. It uses aligned_storage arrays to store
53  * the keys and values to allow constructing and destroying key-value pairs
54  * in place. The lifetime of bucket data should be managed by the container.
55  * It is the user's responsibility to confirm whether the data they are
56  * accessing is live or not.
57  */
58  class bucket {
59  public:
60  bucket() noexcept : occupied_() {}
61 
62  const value_type &kvpair(size_type ind) const {
63  return *static_cast<const value_type *>(
64  static_cast<const void *>(&values_[ind]));
65  }
66  value_type &kvpair(size_type ind) {
67  return *static_cast<value_type *>(static_cast<void *>(&values_[ind]));
68  }
69 
70  const key_type &key(size_type ind) const {
71  return storage_kvpair(ind).first;
72  }
73  key_type &&movable_key(size_type ind) {
74  return std::move(storage_kvpair(ind).first);
75  }
76 
77  const mapped_type &mapped(size_type ind) const {
78  return storage_kvpair(ind).second;
79  }
80  mapped_type &mapped(size_type ind) { return storage_kvpair(ind).second; }
81 
82  partial_t partial(size_type ind) const { return partials_[ind]; }
83  partial_t &partial(size_type ind) { return partials_[ind]; }
84 
85  bool occupied(size_type ind) const { return occupied_[ind]; }
86  bool &occupied(size_type ind) { return occupied_[ind]; }
87 
88  private:
89  friend class bucket_container;
90 
91  using storage_value_type = std::pair<Key, T>;
92 
93  const storage_value_type &storage_kvpair(size_type ind) const {
94  return *static_cast<const storage_value_type *>(
95  static_cast<const void *>(&values_[ind]));
96  }
97  storage_value_type &storage_kvpair(size_type ind) {
98  return *static_cast<storage_value_type *>(
99  static_cast<void *>(&values_[ind]));
100  }
101 
102  std::array<typename std::aligned_storage<sizeof(storage_value_type),
103  alignof(storage_value_type)>::type,
104  SLOT_PER_BUCKET>
105  values_;
106  std::array<partial_t, SLOT_PER_BUCKET> partials_;
107  std::array<bool, SLOT_PER_BUCKET> occupied_;
108  };
109 
110  bucket_container(size_type hp, const allocator_type &allocator)
111  : allocator_(allocator), bucket_allocator_(allocator), hashpower_(hp),
112  buckets_(bucket_allocator_.allocate(size())) {
113  // The bucket default constructor is nothrow, so we don't have to
114  // worry about dealing with exceptions when constructing all the
115  // elements.
116  static_assert(std::is_nothrow_constructible<bucket>::value,
117  "bucket_container requires bucket to be nothrow "
118  "constructible");
119  for (size_type i = 0; i < size(); ++i) {
120  traits_::construct(allocator_, &buckets_[i]);
121  }
122  }
123 
124  ~bucket_container() noexcept { destroy_buckets(); }
125 
126  bucket_container(const bucket_container &bc)
127  : allocator_(
128  traits_::select_on_container_copy_construction(bc.allocator_)),
129  bucket_allocator_(allocator_), hashpower_(bc.hashpower()),
130  buckets_(transfer(bc.hashpower(), bc, std::false_type())) {}
131 
132  bucket_container(const bucket_container &bc,
133  const allocator_type &a)
134  : allocator_(a), bucket_allocator_(allocator_),
135  hashpower_(bc.hashpower()),
136  buckets_(transfer(bc.hashpower(), bc, std::false_type())) {}
137 
138  bucket_container(bucket_container &&bc)
139  : allocator_(std::move(bc.allocator_)), bucket_allocator_(allocator_),
140  hashpower_(bc.hashpower()), buckets_(std::move(bc.buckets_)) {
141  // De-activate the other buckets container
142  bc.buckets_ = nullptr;
143  }
144 
145  bucket_container(bucket_container &&bc,
146  const allocator_type &a)
147  : allocator_(a), bucket_allocator_(allocator_) {
148  move_assign(bc, std::false_type());
149  }
150 
151  bucket_container &operator=(const bucket_container &bc) {
152  destroy_buckets();
153  copy_allocator(allocator_, bc.allocator_,
154  typename traits_::propagate_on_container_copy_assignment());
155  bucket_allocator_ = allocator_;
156  hashpower(bc.hashpower());
157  buckets_ = transfer(bc.hashpower(), bc, std::false_type());
158  return *this;
159  }
160 
161  bucket_container &operator=(bucket_container &&bc) {
162  destroy_buckets();
163  move_assign(bc, typename traits_::propagate_on_container_move_assignment());
164  return *this;
165  }
166 
167  void swap(bucket_container &bc) noexcept {
168  swap_allocator(allocator_, bc.allocator_,
169  typename traits_::propagate_on_container_swap());
170  swap_allocator(bucket_allocator_, bc.bucket_allocator_,
171  typename traits_::propagate_on_container_swap());
172  // Regardless of whether we actually swapped the allocators or not, it will
173  // always be okay to do the remainder of the swap. This is because if the
174  // allocators were swapped, then the subsequent operations are okay. If the
175  // allocators weren't swapped but compare equal, then we're okay. If they
176  // weren't swapped and compare unequal, then behavior is undefined, so
177  // we're okay.
178  size_t bc_hashpower = bc.hashpower();
179  bc.hashpower(hashpower());
180  hashpower(bc_hashpower);
181  std::swap(buckets_, bc.buckets_);
182  }
183 
184  size_type hashpower() const {
185  return hashpower_.load(std::memory_order_acquire);
186  }
187 
188  void hashpower(size_type val) {
189  hashpower_.store(val, std::memory_order_release);
190  }
191 
192  size_type size() const { return size_type(1) << hashpower(); }
193 
194  allocator_type get_allocator() const { return allocator_; }
195 
196  bucket &operator[](size_type i) { return buckets_[i]; }
197  const bucket &operator[](size_type i) const { return buckets_[i]; }
198 
199  // Constructs live data in a bucket
200  template <typename K, typename... Args>
201  void setKV(size_type ind, size_type slot, partial_t p, K &&k,
202  Args &&... args) {
203  bucket &b = buckets_[ind];
204  assert(!b.occupied(slot));
205  b.partial(slot) = p;
206  traits_::construct(allocator_, std::addressof(b.storage_kvpair(slot)),
207  std::piecewise_construct,
208  std::forward_as_tuple(std::forward<K>(k)),
209  std::forward_as_tuple(std::forward<Args>(args)...));
210  // This must occur last, to enforce a strong exception guarantee
211  b.occupied(slot) = true;
212  }
213 
214  // Destroys live data in a bucket
215  void eraseKV(size_type ind, size_type slot) {
216  bucket &b = buckets_[ind];
217  assert(b.occupied(slot));
218  b.occupied(slot) = false;
219  traits_::destroy(allocator_, std::addressof(b.storage_kvpair(slot)));
220  }
221 
222  // Destroys all the live data in the buckets. Does not deallocate the bucket
223  // memory.
224  void clear() noexcept {
225  static_assert(
226  std::is_nothrow_destructible<key_type>::value &&
227  std::is_nothrow_destructible<mapped_type>::value,
228  "bucket_container requires key and value to be nothrow "
229  "destructible");
230  for (size_type i = 0; i < size(); ++i) {
231  bucket &b = buckets_[i];
232  for (size_type j = 0; j < SLOT_PER_BUCKET; ++j) {
233  if (b.occupied(j)) {
234  eraseKV(i, j);
235  }
236  }
237  }
238  }
239 
240  // Destroys and deallocates all data in the buckets. After this operation,
241  // the bucket container will have no allocated data. It is still valid to
242  // swap, move or copy assign to this container.
243  void clear_and_deallocate() noexcept {
244  destroy_buckets();
245  }
246 
247  // Returns true if the bucket container memory has been deallocated, or false
248  // if it still owns any memory. If true, the member-wise getter/setter
249  // operations cannot be called safely. Object-level members (such as
250  // hashpower and size) will remain valid after deallocation.
251  bool is_deallocated() const noexcept {
252  return buckets_ == nullptr;
253  }
254 
255 private:
256  using bucket_traits_ = typename traits_::template rebind_traits<bucket>;
257  using bucket_pointer = typename bucket_traits_::pointer;
258 
259  // true here means the allocators from `src` are propagated on libcuckoo_copy
260  template <typename A>
261  void copy_allocator(A &dst, const A &src, std::true_type) {
262  dst = src;
263  }
264 
265  template <typename A>
266  void copy_allocator(A &dst, const A &src, std::false_type) {}
267 
268  // true here means the allocators from `src` are propagated on libcuckoo_swap
269  template <typename A> void swap_allocator(A &dst, A &src, std::true_type) {
270  std::swap(dst, src);
271  }
272 
273  template <typename A> void swap_allocator(A &, A &, std::false_type) {}
274 
275  // true here means the bucket allocator should be propagated
276  void move_assign(bucket_container &src, std::true_type) {
277  allocator_ = std::move(src.allocator_);
278  bucket_allocator_ = allocator_;
279  hashpower(src.hashpower());
280  buckets_ = src.buckets_;
281  src.buckets_ = nullptr;
282  }
283 
284  void move_assign(bucket_container &src, std::false_type) {
285  hashpower(src.hashpower());
286  if (allocator_ == src.allocator_) {
287  buckets_ = src.buckets_;
288  src.buckets_ = nullptr;
289  } else {
290  buckets_ = transfer(src.hashpower(), src, std::true_type());
291  }
292  }
293 
294  void destroy_buckets() noexcept {
295  if (is_deallocated()) {
296  return;
297  }
298  // The bucket default constructor is nothrow, so we don't have to
299  // worry about dealing with exceptions when constructing all the
300  // elements.
301  static_assert(std::is_nothrow_destructible<bucket>::value,
302  "bucket_container requires bucket to be nothrow "
303  "destructible");
304  clear();
305  for (size_type i = 0; i < size(); ++i) {
306  traits_::destroy(allocator_, &buckets_[i]);
307  }
308  bucket_allocator_.deallocate(buckets_, size());
309  buckets_ = nullptr;
310  }
311 
312  // `true` here refers to whether or not we should move
313  void move_or_copy(size_type dst_ind, size_type dst_slot, bucket &src,
314  size_type src_slot, std::true_type) {
315  setKV(dst_ind, dst_slot, src.partial(src_slot), src.movable_key(src_slot),
316  std::move(src.mapped(src_slot)));
317  }
318 
319  void move_or_copy(size_type dst_ind, size_type dst_slot, bucket &src,
320  size_type src_slot, std::false_type) {
321  setKV(dst_ind, dst_slot, src.partial(src_slot), src.key(src_slot),
322  src.mapped(src_slot));
323  }
324 
325  template <bool B>
326  bucket_pointer transfer(
327  size_type dst_hp,
328  typename std::conditional<B, bucket_container &,
329  const bucket_container &>::type src,
330  std::integral_constant<bool, B> move) {
331  assert(dst_hp >= src.hashpower());
332  if (src.is_deallocated()) {
333  return nullptr;
334  }
335  bucket_container dst(dst_hp, get_allocator());
336  // Move/copy all occupied slots of the source buckets
337  for (size_t i = 0; i < src.size(); ++i) {
338  for (size_t j = 0; j < SLOT_PER_BUCKET; ++j) {
339  if (src.buckets_[i].occupied(j)) {
340  dst.move_or_copy(i, j, src.buckets_[i], j, move);
341  }
342  }
343  }
344  // Take away the pointer from `dst` and return it
345  bucket_pointer dst_pointer = dst.buckets_;
346  dst.buckets_ = nullptr;
347  return dst_pointer;
348  }
349 
350  // This allocator matches the value_type, but is not used to construct
351  // storage_value_type pairs, or allocate buckets
352  allocator_type allocator_;
353  // This allocator is used for actually allocating buckets. It is simply
354  // copy-constructed from `allocator_`, and will always be copied whenever
355  // allocator_ is copied.
356  typename traits_::template rebind_alloc<bucket> bucket_allocator_;
357  // This needs to be atomic, since it can be read and written by multiple
358  // threads not necessarily synchronized by a lock.
359  std::atomic<size_type> hashpower_;
360  // These buckets are protected by striped locks (external to the
361  // BucketContainer), which must be obtained before accessing a bucket.
362  bucket_pointer buckets_;
363 
364  // If the key and value are Trivial, the bucket be serilizable. Since we
365  // already disallow user-specialized instances of std::pair, we know that the
366  // default implementation of std::pair uses a default copy constructor, so
367  // this should be okay. We could in theory just check if the type is
368  // TriviallyCopyable but this check is not available on some compilers we
369  // want to support.
370  template <typename ThisKey, typename ThisT>
371  friend typename std::enable_if<std::is_trivial<ThisKey>::value &&
372  std::is_trivial<ThisT>::value,
373  std::ostream &>::type
374  operator<<(std::ostream &os,
375  const bucket_container<ThisKey, ThisT, Allocator,
376  Partial, SLOT_PER_BUCKET> &bc) {
377  size_type hp = bc.hashpower();
378  os.write(reinterpret_cast<const char *>(&hp), sizeof(size_type));
379  os.write(reinterpret_cast<const char *>(bc.buckets_),
380  sizeof(bucket) * bc.size());
381  return os;
382  }
383 
384  template <typename ThisKey, typename ThisT>
385  friend typename std::enable_if<std::is_trivial<ThisKey>::value &&
386  std::is_trivial<ThisT>::value,
387  std::istream &>::type
388  operator>>(std::istream &is,
389  bucket_container<ThisKey, ThisT, Allocator,
390  Partial, SLOT_PER_BUCKET> &bc) {
391  size_type hp;
392  is.read(reinterpret_cast<char *>(&hp), sizeof(size_type));
393  bucket_container new_bc(hp, bc.get_allocator());
394  is.read(reinterpret_cast<char *>(new_bc.buckets_),
395  new_bc.size() * sizeof(bucket));
396  bc.swap(new_bc);
397  return is;
398  }
399 };
400 
401 } // namespace libcuckoo
402 
403 #endif // BUCKET_CONTAINER_H
libcuckoo::bucket_container
Definition: bucket_container.hh:31
cuckoohash_util.hh
libcuckoo::bucket_container::bucket
Definition: bucket_container.hh:58