Vlasiator ebf0dd394 on dev (v5.4.0 + 1054 commits)
Loading...
Searching...
No Matches
open_bucket_hashtable.h
Go to the documentation of this file.
1/*
2 * This file is part of Vlasiator.
3 * Copyright 2010-2016 Finnish Meteorological Institute
4 *
5 * For details of usage, see the COPYING file and read the "Rules of the Road"
6 * at http://www.physics.helsinki.fi/vlasiator/
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22#pragma once
23
24#include <algorithm>
25#include <vector>
26#include <stdexcept>
27#include <cassert>
28#include "definitions.h"
29
30// Open bucket power-of-two sized hash table with multiplicative fibonacci hashing
31template <typename GID, typename LID, int maxBucketOverflow = 4, GID EMPTYBUCKET = vmesh::INVALID_GLOBALID > class OpenBucketHashtable {
32private:
33 int sizePower; // Logarithm (base two) of the size of the table
34 size_t fill; // Number of filled buckets
35 std::vector<std::pair<GID, LID>> buckets;
36
37 // Fibonacci hash function for 64bit values
38 uint32_t fibonacci_hash(GID in) const {
39 in ^= in >> (32 - sizePower);
40 uint32_t retval = (uint64_t)(in * 2654435769ul) >> (32 - sizePower);
41 return retval;
42 }
43
44 //Hash a chunk of memory using fnv_1a
45 static uint32_t fnv_1a(const void* chunk, size_t bytes) {
46 assert(chunk);
47 uint32_t h = 2166136261ul;
48 const unsigned char* ptr = static_cast<const unsigned char*>(chunk);
49 while (bytes--){
50 h = (h ^ *ptr++) * 16777619ul;
51 }
52 return h ;
53 }
54
55 // Generic h
56 uint32_t hash(GID in) const {
57 static constexpr bool n = (std::is_arithmetic<GID>::value && sizeof(GID) <= sizeof(uint32_t));
58
59 if (n) {
60 return fibonacci_hash(in);
61 } else {
62 return fnv_1a(&in, sizeof(GID));
63 }
64 }
65
66public:
67 OpenBucketHashtable() : sizePower(4), fill(0), buckets(1 << sizePower, std::pair<GID, LID>(EMPTYBUCKET, LID())) {};
68
69 // Resize the table to fit more things. This is automatically invoked once
70 // maxBucketOverflow has triggered.
71 void rehash(int newSizePower) {
72 if (newSizePower > 31) {
73 throw std::out_of_range("OpenBucketHashtable ran into rehashing catastrophe and exceeded 32bit buckets.");
74 }
75 std::vector<std::pair<GID, LID>> newBuckets(1u << newSizePower, std::pair<GID, LID>(EMPTYBUCKET, LID()));
76 sizePower = newSizePower;
77 int bitMask = (1u << sizePower) - 1; // For efficient modulo of the array size
78
79 // Iterate through all old elements and rehash them into the new array.
80 for (auto& e : buckets) {
81 // Skip empty buckets
82 if (e.first == EMPTYBUCKET) {
83 continue;
84 }
85
86 uint32_t newHash = hash(e.first);
87 bool found = false;
88 for (int i = 0; i < maxBucketOverflow; i++) {
89 std::pair<GID, LID>& candidate = newBuckets[(newHash + i) & bitMask];
90 if (candidate.first == EMPTYBUCKET) {
91 // Found an empty bucket, assign that one.
92 candidate = e;
93 found = true;
94 break;
95 }
96 }
97
98 if (!found) {
99 // Having arrived here means that we unsuccessfully rehashed and
100 // are *still* overflowing our buckets. So we need to try again with a bigger one.
101 return rehash(newSizePower + 1);
102 }
103 }
104
105 // Replace our buckets with the new ones
106 buckets = newBuckets;
107 }
108
109 // Element access (by reference). Nonexistent elements get created.
110 LID& at(const GID& key) {
111 int bitMask = (1 << sizePower) - 1; // For efficient modulo of the array size
112 uint32_t hashIndex = hash(key);
113
114 // Try to find the matching bucket.
115 for (int i = 0; i < maxBucketOverflow; i++) {
116 std::pair<GID, LID>& candidate = buckets[(hashIndex + i) & bitMask];
117 if (candidate.first == key) {
118 // Found a match, return that
119 return candidate.second;
120 }
121 if (candidate.first == EMPTYBUCKET) {
122 // Found an empty bucket, assign and return that.
123 candidate.first = key;
124 fill++;
125 return candidate.second;
126 }
127 }
128
129 // Not found, and we have no free slots to create a new one. So we need to rehash to a larger size.
130 rehash(sizePower + 1);
131 return at(key); // Recursive tail call to try again with larger table.
132 }
133
134 const LID& at(const GID& key) const {
135 int bitMask = (1 << sizePower) - 1; // For efficient modulo of the array size
136 uint32_t hashIndex = hash(key);
137
138 // Try to find the matching bucket.
139 for (int i = 0; i < maxBucketOverflow; i++) {
140 const std::pair<GID, LID>& candidate = buckets[(hashIndex + i) & bitMask];
141 if (candidate.first == key) {
142 // Found a match, return that
143 return candidate.second;
144 }
145 if (candidate.first == EMPTYBUCKET) {
146 // Found an empty bucket, so error.
147 throw std::out_of_range("Element not found in OpenBucketHashtable.at");
148 }
149 }
150
151 // Not found, so error.
152 throw std::out_of_range("Element not found in OpenBucketHashtable.at");
153 }
154
155 // Typical array-like access with [] operator
156 LID& operator[](const GID& key) { return at(key); }
157
158 // For STL compatibility: size(), bucket_count(), count(GID), clear()
159 size_t size() const { return fill; }
160
161 size_t bucket_count() const { return buckets.size(); }
162
163 size_t count(const GID& key) const {
164 if (find(key) != end()) {
165 return 1;
166 } else {
167 return 0;
168 }
169 }
170
171 void clear() {
172 buckets = std::vector<std::pair<GID, LID>>(1 << sizePower, {EMPTYBUCKET, LID()});
173 fill = 0;
174 }
175
176 // Iterator type. Iterates through all non-empty buckets.
177 class iterator {
179 size_t index;
180
181 public:
182 // Define iterator traits
183 using iterator_category = std::random_access_iterator_tag;
184 using value_type = std::pair<GID, LID>;
185 using difference_type = std::ptrdiff_t;
186 using pointer = std::pair<GID, LID>*;
187 using reference = std::pair<GID, LID>&;
188
190
192 index++;
193 while(index < hashtable->buckets.size()){
194 if (hashtable->buckets[index].first != EMPTYBUCKET){
195 break;
196 }
197 index++;
198 }
199 return *this;
200 }
201
202 iterator operator++(int) { // Postfix version
203 iterator temp = *this;
204 ++(*this);
205 return temp;
206 }
207
208 bool operator==(iterator other) const {
209 // comparison of iterators between two different hashtables undefined
210 assert(hashtable == other.hashtable);
211 return index == other.index;
212 }
213 bool operator!=(iterator other) const {
214 return !(*this == other);
215 }
216 std::pair<GID, LID>& operator*() const { return hashtable->buckets[index]; }
217 std::pair<GID, LID>* operator->() const { return &hashtable->buckets[index]; }
218 size_t getIndex() { return index; }
219 };
220
221 // Const iterator.
224 size_t index;
225
226 public:
227 // Define iterator traits
228 using iterator_category = std::random_access_iterator_tag;
229 using value_type = std::pair<GID, LID>;
230 using difference_type = std::ptrdiff_t;
231 using pointer = std::pair<GID, LID>*;
232 using reference = std::pair<GID, LID>&;
233
235
237 index++;
238 while(index < hashtable->buckets.size()){
239 if (hashtable->buckets[index].first != EMPTYBUCKET){
240 break;
241 }
242 index++;
243 }
244 return *this;
245 }
246 const_iterator operator++(int) { // Postfix version
247 const_iterator temp = *this;
248 ++(*this);
249 return temp;
250 }
251
252 bool operator==(const_iterator other) const {
253 // comparison of iterators between two different hashtables undefined
254 assert(hashtable == other.hashtable);
255 return index == other.index;
256 }
257 bool operator!=(const_iterator other) const {
258 return !(*this == other);
259 }
260 const std::pair<GID, LID>& operator*() const { return hashtable->buckets[index]; }
261 const std::pair<GID, LID>* operator->() const { return &hashtable->buckets[index]; }
262 size_t getIndex() { return index; }
263 };
264
266 for (size_t i = 0; i < buckets.size(); i++) {
267 if (buckets[i].first != EMPTYBUCKET) {
268 return iterator(this, i);
269 }
270 }
271 return end();
272 }
274 for (size_t i = 0; i < buckets.size(); i++) {
275 if (buckets[i].first != EMPTYBUCKET) {
276 return const_iterator(this, i);
277 }
278 }
279 return end();
280 }
281
282 iterator end() { return iterator(this, buckets.size()); }
283 const_iterator end() const { return const_iterator(this, buckets.size()); }
284
285 // Element access by iterator
286 iterator find(GID key) {
287 int bitMask = (1 << sizePower) - 1; // For efficient modulo of the array size
288 uint32_t hashIndex = hash(key);
289
290 // Try to find the matching bucket.
291 for (int i = 0; i < maxBucketOverflow; i++) {
292 const std::pair<GID, LID>& candidate = buckets[(hashIndex + i) & bitMask];
293 if (candidate.first == key) {
294 // Found a match, return that
295 return iterator(this, (hashIndex + i) & bitMask);
296 }
297
298 if (candidate.first == EMPTYBUCKET) {
299 // Found an empty bucket. Return empty.
300 return end();
301 }
302 }
303
304 // Not found
305 return end();
306 }
307
308 const const_iterator find(GID key) const {
309 int bitMask = (1 << sizePower) - 1; // For efficient modulo of the array size
310 uint32_t hashIndex = hash(key);
311
312 // Try to find the matching bucket.
313 for (int i = 0; i < maxBucketOverflow; i++) {
314 const std::pair<GID, LID>& candidate = buckets[(hashIndex + i) & bitMask];
315 if (candidate.first == key) {
316 // Found a match, return that
317 return const_iterator(this, (hashIndex + i) & bitMask);
318 }
319
320 if (candidate.first == EMPTYBUCKET) {
321 // Found an empty bucket. Return empty.
322 return end();
323 }
324 }
325
326 // Not found
327 return end();
328 }
329
330 // More STL compatibility implementations
331 std::pair<iterator, bool> insert(std::pair<GID, LID> newEntry) {
332 bool found = find(newEntry.first) != end();
333 if (!found) {
334 at(newEntry.first) = newEntry.second;
335 }
336 return std::pair<iterator, bool>(find(newEntry.first), !found);
337 }
338
339 // Remove one element from the hash table.
341 // Due to overflowing buckets, this might require moving quite a bit of stuff around.
342 size_t index = keyPos.getIndex();
343
344 if (buckets[index].first != EMPTYBUCKET) {
345 // Decrease fill count
346 fill--;
347
348 // Clear the element itself.
349 buckets[index].first = EMPTYBUCKET;
350
351 int bitMask = (1 << sizePower) - 1; // For efficient modulo of the array size
352 size_t targetPos = index;
353 // Search ahead to verify items are in correct places (until empty bucket is found)
354 for (unsigned int i = 1; i < fill; i++) {
355 GID nextBucket = buckets[(index + i)&bitMask].first;
356 if (nextBucket == EMPTYBUCKET) {
357 // The next bucket is empty, we are done.
358 break;
359 }
360 // Found an entry: is it in the correct bucket?
361 uint32_t hashIndex = hash(nextBucket);
362 if ((hashIndex&bitMask) != ((index + i)&bitMask)) {
363 // This entry has overflown. Now check if it should be moved:
364 uint32_t distance = ((targetPos - hashIndex + (1<<sizePower) )&bitMask);
365 if (distance < maxBucketOverflow) {
366 // Copy this entry to the current newly empty bucket, then continue with deleting
367 // this overflown entry and continue searching for overflown entries
368 LID moveValue = buckets[(index+i)&bitMask].second;
369 buckets[targetPos] = std::pair<GID, LID>(nextBucket,moveValue);
370 targetPos = ((index+i)&bitMask);
371 buckets[targetPos].first = EMPTYBUCKET;
372 }
373 }
374 }
375 }
376 // return the next valid bucket member
377 ++keyPos;
378 return keyPos;
379 }
380 size_t erase(const GID& key) {
381 iterator element = find(key);
382 if(element == end()) {
383 return 0;
384 } else {
385 erase(element);
386 return 1;
387 }
388 }
389
391 buckets.swap(other.buckets);
392 int tempSizePower = sizePower;
393 sizePower = other.sizePower;
394 other.sizePower = tempSizePower;
395
396 size_t tempFill = fill;
397 fill = other.fill;
398 other.fill = tempFill;
399 }
400};
for i
Definition Dispersion.m:24
std::random_access_iterator_tag iterator_category
const std::pair< GID, LID > & operator*() const
bool operator==(const_iterator other) const
bool operator!=(const_iterator other) const
const std::pair< GID, LID > * operator->() const
const_iterator(const OpenBucketHashtable< GID, LID > *hashtable, size_t index)
const OpenBucketHashtable< GID, LID > * hashtable
OpenBucketHashtable< GID, LID > * hashtable
std::random_access_iterator_tag iterator_category
bool operator==(iterator other) const
std::pair< GID, LID > * operator->() const
bool operator!=(iterator other) const
iterator(OpenBucketHashtable< GID, LID > *hashtable, size_t index)
std::pair< GID, LID > & operator*() const
LID & at(const GID &key)
const_iterator end() const
const_iterator begin() const
void swap(OpenBucketHashtable< GID, LID > &other)
void rehash(int newSizePower)
static uint32_t fnv_1a(const void *chunk, size_t bytes)
std::pair< iterator, bool > insert(std::pair< GID, LID > newEntry)
const LID & at(const GID &key) const
size_t erase(const GID &key)
iterator erase(iterator keyPos)
uint32_t fibonacci_hash(GID in) const
const const_iterator find(GID key) const
uint32_t hash(GID in) const
std::vector< std::pair< GID, LID > > buckets
LID & operator[](const GID &key)
size_t count(const GID &key) const
#define index(i, j, k)