├── CMakeLists.txt ├── README.md ├── include ├── hash_map.h ├── hash_set.h ├── linked_hash_map.h └── linked_hash_set.h └── tests ├── bench_map.cc ├── bytes.h ├── sha256.c ├── sha256.h ├── test_hash_map.cc ├── test_hash_set.cc ├── test_linked_hash_map.cc ├── test_linked_hash_set.cc └── test_sha_delta.cc /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(ethical_hashmap) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 7 | 8 | include_directories(include) 9 | 10 | add_executable(bench_map tests/bench_map.cc) 11 | add_executable(test_hash_map tests/test_hash_map.cc) 12 | add_executable(test_hash_set tests/test_hash_set.cc tests/sha256.c) 13 | add_executable(test_linked_hash_map tests/test_linked_hash_map.cc) 14 | add_executable(test_linked_hash_set tests/test_linked_hash_set.cc tests/sha256.c) 15 | add_executable(test_sha_delta tests/test_sha_delta.cc tests/sha256.c) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ethical hash map 2 | 3 | High performance, compact and ethical C++ hash sets and maps: 4 | 5 | > ### _hash_set_ 6 | > - Fast open addressing hash set tuned for small sets. 7 | > 8 | > ### _hash_map_ 9 | > - Fast open addressing hash map tuned for small maps. 10 | > 11 | > ### _linked_hash_set_ 12 | > - Fast open addressing hash set with link list tuned 13 | > for small maps with predictable iteration order. 14 | > 15 | > ### _linked_hash_map_ 16 | > - Fast open addressing hash map with link list tuned 17 | > for small maps with predictable iteration order. 18 | 19 | These hash sets and maps are open-addressing hashtables similar 20 | to `google/dense_hash_map`, but they use tombstone bitmaps to 21 | eliminate the necessity for empty or deleted key sentinels. 22 | 23 | These containers implement most of the C++ unordered associative 24 | container requrements thus can be substituted for `unordered_map` 25 | and typical use cases including C++11 _for-loop_. 26 | 27 | There is currently no equivalent to `linked_hash_map` in the STL so it 28 | is perhaps somewhat like an `ordered_map` that uses the _pos_ iterator 29 | hint in the first argument of _insert_ to set the position of a new 30 | value inserted into the linked list, while _find_ uses the hashed key 31 | index to locate _key value pairs_ in the hash table. 32 | 33 | ## Design 34 | 35 | These maps are designed for a small memory footprint. There is one 36 | structure and one heap allocation which is divided between an array 37 | of _tuples_ composed of _(Key, Value)_ pairs and a tombstone bitmap. 38 | _Key_ is hashed to index into the open addressing array, and 39 | collisions are handled by skipping to the next available slot. 40 | 41 | Deletions requires the use of tombstones. Sentinel key values occupy 42 | key-space, and make containers incompatible with associative container 43 | requirements, thus a 2-bit tombstone array is used. 2-bits are used 44 | to distinguish _available_, _occupied_, and _deleted_ states. 45 | 46 | ``` 47 | enum bitmap_state { 48 | available = 0, occupied = 1, deleted = 2, recycled = 3 49 | }; 50 | ``` 51 | 52 | _linked_hash_map_ adds _(next, prev)_ indices to the array _tuple_, 53 | _(head, tail)_ indices to the structure. 54 | 55 | ### Memory usage 56 | 57 | The follow table shows memory usage for the default 16 slot map 58 | _(table units are in bytes)_: 59 | 60 | | map | struct | malloc | 61 | |:----------------------------- | ----------:| ------:| 62 | |_hash_set_ | 40 | 68 | 63 | |_hash_set_ | 40 | 132 | 64 | |_hash_map_ | 40 | 132 | 65 | |_hash_map_ | 40 | 260 | 66 | |_linked_hash_set_ | 48 | 198 | 67 | |_linked_hash_set_ | 56 | 388 | 68 | |_linked_hash_map_ | 48 | 260 | 69 | |_linked_hash_map_ | 56 | 516 | 70 | 71 | The following table shows the structure size in bytes on _x86_64_: 72 | 73 | | map | size | 74 | |:----------------------------------| ----:| 75 | |_sizeof(hash_map)_ | 40 | 76 | |_sizeof(linked_hash_map)_ | 48 | 77 | |_sizeof(absl::flat_hash_map)_ | 48 | 78 | |_sizeof(std::unordered_map)_ | 56 | 79 | |_sizeof(tsl::robin_map)_ | 80 | 80 | |_sizeof(google::dense_hash_map)_ | 88 | 81 | 82 | _linked_hash_map_ by default uses 32-bit integers for indices, 83 | limiting it to 2^31 entries, however, it can be instantiated 84 | with alternative integer types for indices. 85 | 86 | ## Build Instructions 87 | 88 | ``` 89 | cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo 90 | cmake --build build 91 | ``` 92 | -------------------------------------------------------------------------------- /include/hash_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Fast open addressing hash table with tombstone bit map. 3 | * 4 | * Copyright (c) 2020 Michael Clark 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | namespace ethical { 31 | 32 | /* 33 | * This open addressing hash_map uses a 2-bit entry per slot bitmap 34 | * that eliminates the need for empty and deleted key sentinels. 35 | * The hash_map has a simple array of key and value pairs and the 36 | * tombstone bitmap, which are allocated in a single call to malloc. 37 | */ 38 | 39 | template , 41 | class Pred = std::equal_to> 42 | struct hash_map 43 | { 44 | static const size_t default_size = (2<<3); /* 16 */ 45 | static const size_t load_factor = (2<<15); /* 0.5 */ 46 | static const size_t load_multiplier = (2<<16); /* 1.0 */ 47 | 48 | static inline Hash _hasher; 49 | static inline Pred _compare; 50 | 51 | struct data_type { 52 | Key first; 53 | Value second; 54 | }; 55 | 56 | typedef Key key_type; 57 | typedef Value mapped_type; 58 | typedef std::pair value_type; 59 | typedef Hash hasher; 60 | typedef Pred key_equal; 61 | typedef data_type& reference; 62 | typedef const data_type& const_reference; 63 | 64 | size_t used; 65 | size_t tombs; 66 | size_t limit; 67 | data_type *data; 68 | uint64_t *bitmap; 69 | 70 | /* 71 | * scanning iterator 72 | */ 73 | 74 | struct iterator 75 | { 76 | hash_map *h; 77 | size_t i; 78 | 79 | size_t step(size_t i) { 80 | while (i < h->limit && 81 | (bitmap_get(h->bitmap, i) & occupied) != occupied) i++; 82 | return i; 83 | } 84 | iterator& operator++() { i = step(i+1); return *this; } 85 | iterator operator++(int) { iterator r = *this; ++(*this); return r; } 86 | data_type& operator*() { i = step(i); return h->data[i]; } 87 | data_type* operator->() { i = step(i); return &h->data[i]; } 88 | bool operator==(const iterator &o) const { return h == o.h && i == o.i; } 89 | bool operator!=(const iterator &o) const { return h != o.h || i != o.i; } 90 | }; 91 | 92 | /* 93 | * constructors and destructor 94 | */ 95 | 96 | inline hash_map() : hash_map(default_size) {} 97 | inline hash_map(size_t initial_size) : 98 | used(0), tombs(0), limit(initial_size) 99 | { 100 | size_t data_size = sizeof(data_type) * limit; 101 | size_t bitmap_size = bitmap_capacity(limit); 102 | size_t total_size = data_size + bitmap_size; 103 | 104 | assert(is_pow2(limit)); 105 | 106 | data = (data_type*)malloc(total_size); 107 | bitmap = (uint64_t*)((char*)data + data_size); 108 | memset(bitmap, 0, bitmap_size); 109 | } 110 | 111 | inline ~hash_map() 112 | { 113 | if (data) { 114 | for (size_t i = 0; i < limit; i++) { 115 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 116 | data[i].~data_type(); 117 | } 118 | } 119 | free(data); 120 | } 121 | } 122 | 123 | /* 124 | * copy constructor and assignment operator 125 | */ 126 | 127 | inline hash_map(const hash_map &o) : 128 | used(o.used), tombs(o.tombs), limit(o.limit) 129 | { 130 | size_t data_size = sizeof(data_type) * limit; 131 | size_t bitmap_size = bitmap_capacity(limit); 132 | size_t total_size = data_size + bitmap_size; 133 | 134 | data = (data_type*)malloc(total_size); 135 | bitmap = (uint64_t*)((char*)data + data_size); 136 | 137 | memcpy(bitmap, o.bitmap, bitmap_size); 138 | for (size_t i = 0; i < limit; i++) { 139 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 140 | data[i] = /* copy */ o.data[i]; 141 | } 142 | } 143 | } 144 | 145 | inline hash_map(hash_map &&o) : 146 | used(o.used), tombs(o.tombs), limit(o.limit), 147 | data(o.data), bitmap(o.bitmap) 148 | { 149 | o.data = nullptr; 150 | o.bitmap = nullptr; 151 | } 152 | 153 | inline hash_map& operator=(const hash_map &o) 154 | { 155 | free(data); 156 | 157 | used = o.used; 158 | tombs = o.tombs; 159 | limit = o.limit; 160 | 161 | size_t data_size = sizeof(data_type) * limit; 162 | size_t bitmap_size = bitmap_capacity(limit); 163 | size_t total_size = data_size + bitmap_size; 164 | 165 | data = (data_type*)malloc(total_size); 166 | bitmap = (uint64_t*)((char*)data + data_size); 167 | 168 | memcpy(bitmap, o.bitmap, bitmap_size); 169 | for (size_t i = 0; i < limit; i++) { 170 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 171 | data[i] = /* copy */ o.data[i]; 172 | } 173 | } 174 | 175 | return *this; 176 | } 177 | 178 | inline hash_map& operator=(hash_map &&o) 179 | { 180 | data = o.data; 181 | bitmap = o.bitmap; 182 | used = o.used; 183 | tombs = o.tombs; 184 | limit = o.limit; 185 | 186 | o.data = nullptr; 187 | o.bitmap = nullptr; 188 | 189 | return *this; 190 | } 191 | 192 | /* 193 | * member functions 194 | */ 195 | 196 | inline size_t size() { return used; } 197 | inline size_t capacity() { return limit; } 198 | inline size_t load() { return (used + tombs) * load_multiplier / limit; } 199 | inline size_t index_mask() { return limit - 1; } 200 | inline size_t hash_index(uint64_t h) { return h & index_mask(); } 201 | inline size_t key_index(Key key) { return hash_index(_hasher(key)); } 202 | inline hasher hash_function() const { return _hasher; } 203 | inline iterator begin() { return iterator{ this, 0 }; } 204 | inline iterator end() { return iterator{ this, limit }; } 205 | 206 | /* 207 | * bit manipulation helpers 208 | */ 209 | 210 | enum bitmap_state { 211 | available = 0, occupied = 1, deleted = 2, recycled = 3 212 | }; 213 | static inline size_t bitmap_capacity(size_t limit) 214 | { 215 | return (((limit + 3) >> 2) + 7) & ~7; 216 | } 217 | static inline size_t bitmap_idx(size_t i) { return i >> 5; } 218 | static inline size_t bitmap_shift(size_t i) { return ((i << 1) & 63); } 219 | static inline bitmap_state bitmap_get(uint64_t *bitmap, size_t i) 220 | { 221 | return (bitmap_state)((bitmap[bitmap_idx(i)] >> bitmap_shift(i)) & 3); 222 | } 223 | static inline void bitmap_set(uint64_t *bitmap, size_t i, uint64_t value) 224 | { 225 | bitmap[bitmap_idx(i)] |= (value << bitmap_shift(i)); 226 | } 227 | static inline void bitmap_clear(uint64_t *bitmap, size_t i, uint64_t value) 228 | { 229 | bitmap[bitmap_idx(i)] &= ~(value << bitmap_shift(i)); 230 | } 231 | static inline bool is_pow2(intptr_t n) { return ((n & -n) == n); } 232 | 233 | /** 234 | * the implementation 235 | */ 236 | 237 | void resize_internal(data_type *old_data, uint64_t *old_bitmap, 238 | size_t old_limit, size_t new_limit) 239 | { 240 | size_t data_size = sizeof(data_type) * new_limit; 241 | size_t bitmap_size = bitmap_capacity(new_limit); 242 | size_t total_size = data_size + bitmap_size; 243 | 244 | assert(is_pow2(new_limit)); 245 | 246 | data = (data_type*)malloc(total_size); 247 | bitmap = (uint64_t*)((char*)data + data_size); 248 | limit = new_limit; 249 | memset(bitmap, 0, bitmap_size); 250 | 251 | size_t i = 0; 252 | for (data_type *v = old_data; v != old_data + old_limit; v++, i++) { 253 | if ((bitmap_get(old_bitmap, i) & occupied) != occupied) continue; 254 | for (size_t j = key_index(v->first); ; j = (j+1) & index_mask()) { 255 | if ((bitmap_get(bitmap, j) & occupied) != occupied) { 256 | bitmap_set(bitmap, j, occupied); 257 | new (&data[j]) data_type(); 258 | data[j] = /* copy */ *v; 259 | break; 260 | } 261 | } 262 | } 263 | 264 | tombs = 0; 265 | for (size_t i = 0; i < old_limit; i++) { 266 | if ((bitmap_get(old_bitmap, i) & occupied) == occupied) { 267 | old_data[i].~data_type(); 268 | } 269 | } 270 | free(old_data); 271 | } 272 | 273 | void clear() 274 | { 275 | for (size_t i = 0; i < limit; i++) { 276 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 277 | data[i].~data_type(); 278 | } 279 | } 280 | size_t bitmap_size = bitmap_capacity(limit); 281 | memset(bitmap, 0, bitmap_size); 282 | used = tombs = 0; 283 | } 284 | 285 | iterator insert(iterator i, const value_type& val) { return insert(val); } 286 | iterator insert(Key key, Value val) { return insert(value_type(key, val)); } 287 | 288 | iterator insert(const value_type& v) 289 | { 290 | for (size_t i = key_index(v.first); ; i = (i+1) & index_mask()) { 291 | bitmap_state state = bitmap_get(bitmap, i); 292 | if ((state & recycled) == available) { 293 | bitmap_set(bitmap, i, occupied); 294 | new (&data[i]) data_type(); 295 | data[i] = /* copy */ data_type{v.first, v.second}; 296 | used++; 297 | if ((state & deleted) == deleted) tombs--; 298 | if (load() > load_factor) { 299 | resize_internal(data, bitmap, limit, limit << 1); 300 | for (i = key_index(v.first); ; i = (i+1) & index_mask()) { 301 | bitmap_state state = bitmap_get(bitmap, i); 302 | if (state == available) abort(); 303 | else if (state == deleted); /* skip */ 304 | else if (_compare(data[i].first, v.first)) { 305 | return iterator{this, i}; 306 | } 307 | } 308 | } else { 309 | return iterator{this, i}; 310 | } 311 | } else if (_compare(data[i].first, v.first)) { 312 | data[i].second = /* copy */ v.second; 313 | return iterator{this, i}; 314 | } 315 | } 316 | } 317 | 318 | Value& operator[](const Key &key) 319 | { 320 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 321 | bitmap_state state = bitmap_get(bitmap, i); 322 | if ((state & recycled) == available) { 323 | bitmap_set(bitmap, i, occupied); 324 | new (&data[i]) data_type(); 325 | data[i].first = /* copy */ key; 326 | used++; 327 | if ((state & deleted) == deleted) tombs--; 328 | if (load() > load_factor) { 329 | resize_internal(data, bitmap, limit, limit << 1); 330 | for (i = key_index(key);; i = (i+1) & index_mask()) { 331 | bitmap_state state = bitmap_get(bitmap, i); 332 | if (state == available) abort(); 333 | else if (state == deleted); /* skip */ 334 | else if (_compare(data[i].first, key)) { 335 | return data[i].second; 336 | } 337 | } 338 | } 339 | return data[i].second; 340 | } else if (_compare(data[i].first, key)) { 341 | return data[i].second; 342 | } 343 | } 344 | } 345 | 346 | iterator find(const Key &key) 347 | { 348 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 349 | bitmap_state state = bitmap_get(bitmap, i); 350 | if (state == available) /* notfound */ break; 351 | else if (state == deleted); /* skip */ 352 | else if (_compare(data[i].first, key)) return iterator{this, i}; 353 | } 354 | return end(); 355 | } 356 | 357 | void erase(Key key) 358 | { 359 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 360 | bitmap_state state = bitmap_get(bitmap, i); 361 | if (state == available) /* notfound */ break; 362 | else if (state == deleted); /* skip */ 363 | else if (_compare(data[i].first, key)) { 364 | bitmap_set(bitmap, i, deleted); 365 | data[i].~data_type(); 366 | bitmap_clear(bitmap, i, occupied); 367 | used--; 368 | tombs++; 369 | return; 370 | } 371 | } 372 | } 373 | 374 | bool operator==(const hash_map &o) const 375 | { 376 | for (auto i : const_cast(*this)) { 377 | auto j = const_cast(o).find(i.first); 378 | if (j == const_cast(o).end()) return false; 379 | if (i.second != j->second) return false; 380 | } 381 | for (auto i : const_cast(o)) { 382 | auto j = const_cast(*this).find(i.first); 383 | if (j == const_cast(*this).end()) return false; 384 | if (i.second != j->second) return false; 385 | } 386 | return true; 387 | } 388 | 389 | bool operator!=(const hash_map &o) const { return !(*this == o); } 390 | }; 391 | 392 | }; -------------------------------------------------------------------------------- /include/hash_set.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Fast open addressing hash table with tombstone bit map. 3 | * 4 | * Copyright (c) 2020 Michael Clark 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | namespace ethical { 31 | 32 | /* 33 | * This open addressing hash_set uses a 2-bit entry per slot bitmap 34 | * that eliminates the need for empty and deleted key sentinels. 35 | * The hash_set has a simple array of key and value pairs and the 36 | * tombstone bitmap, which are allocated in a single call to malloc. 37 | */ 38 | 39 | template , 41 | class Pred = std::equal_to> 42 | struct hash_set 43 | { 44 | static const size_t default_size = (2<<3); /* 16 */ 45 | static const size_t load_factor = (2<<15); /* 0.5 */ 46 | static const size_t load_multiplier = (2<<16); /* 1.0 */ 47 | 48 | static inline Hash _hasher; 49 | static inline Pred _compare; 50 | 51 | struct data_type { 52 | Key first; 53 | }; 54 | 55 | typedef Key value_type; 56 | typedef Hash hasher; 57 | typedef Pred key_equal; 58 | typedef data_type& reference; 59 | typedef const data_type& const_reference; 60 | 61 | size_t used; 62 | size_t tombs; 63 | size_t limit; 64 | data_type *data; 65 | uint64_t *bitmap; 66 | 67 | /* 68 | * scanning iterator 69 | */ 70 | 71 | struct iterator 72 | { 73 | hash_set *h; 74 | size_t i; 75 | 76 | size_t step(size_t i) { 77 | while (i < h->limit && 78 | (bitmap_get(h->bitmap, i) & occupied) != occupied) i++; 79 | return i; 80 | } 81 | iterator& operator++() { i = step(i+1); return *this; } 82 | iterator operator++(int) { iterator r = *this; ++(*this); return r; } 83 | data_type& operator*() { i = step(i); return h->data[i]; } 84 | data_type* operator->() { i = step(i); return &h->data[i]; } 85 | bool operator==(const iterator &o) const { return h == o.h && i == o.i; } 86 | bool operator!=(const iterator &o) const { return h != o.h || i != o.i; } 87 | }; 88 | 89 | /* 90 | * constructors and destructor 91 | */ 92 | 93 | inline hash_set() : hash_set(default_size) {} 94 | inline hash_set(size_t initial_size) : 95 | used(0), tombs(0), limit(initial_size) 96 | { 97 | size_t data_size = sizeof(data_type) * limit; 98 | size_t bitmap_size = bitmap_capacity(limit); 99 | size_t total_size = data_size + bitmap_size; 100 | 101 | assert(is_pow2(limit)); 102 | 103 | data = (data_type*)malloc(total_size); 104 | bitmap = (uint64_t*)((char*)data + data_size); 105 | memset(bitmap, 0, bitmap_size); 106 | } 107 | 108 | inline ~hash_set() 109 | { 110 | if (data) { 111 | for (size_t i = 0; i < limit; i++) { 112 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 113 | data[i].~data_type(); 114 | } 115 | } 116 | free(data); 117 | } 118 | } 119 | 120 | /* 121 | * copy constructor and assignment operator 122 | */ 123 | 124 | inline hash_set(const hash_set &o) : 125 | used(o.used), tombs(o.tombs), limit(o.limit) 126 | { 127 | size_t data_size = sizeof(data_type) * limit; 128 | size_t bitmap_size = bitmap_capacity(limit); 129 | size_t total_size = data_size + bitmap_size; 130 | 131 | data = (data_type*)malloc(total_size); 132 | bitmap = (uint64_t*)((char*)data + data_size); 133 | 134 | memcpy(bitmap, o.bitmap, bitmap_size); 135 | for (size_t i = 0; i < limit; i++) { 136 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 137 | data[i] = /* copy */ o.data[i]; 138 | } 139 | } 140 | } 141 | 142 | inline hash_set(hash_set &&o) : 143 | used(o.used), tombs(o.tombs), limit(o.limit), 144 | data(o.data), bitmap(o.bitmap) 145 | { 146 | o.data = nullptr; 147 | o.bitmap = nullptr; 148 | } 149 | 150 | inline hash_set& operator=(const hash_set &o) 151 | { 152 | free(data); 153 | 154 | used = o.used; 155 | tombs = o.tombs; 156 | limit = o.limit; 157 | 158 | size_t data_size = sizeof(data_type) * limit; 159 | size_t bitmap_size = bitmap_capacity(limit); 160 | size_t total_size = data_size + bitmap_size; 161 | 162 | data = (data_type*)malloc(total_size); 163 | bitmap = (uint64_t*)((char*)data + data_size); 164 | 165 | memcpy(bitmap, o.bitmap, bitmap_size); 166 | for (size_t i = 0; i < limit; i++) { 167 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 168 | data[i] = /* copy */ o.data[i]; 169 | } 170 | } 171 | 172 | return *this; 173 | } 174 | 175 | inline hash_set& operator=(hash_set &&o) 176 | { 177 | data = o.data; 178 | bitmap = o.bitmap; 179 | used = o.used; 180 | tombs = o.tombs; 181 | limit = o.limit; 182 | 183 | o.data = nullptr; 184 | o.bitmap = nullptr; 185 | 186 | return *this; 187 | } 188 | 189 | /* 190 | * member functions 191 | */ 192 | 193 | inline size_t size() { return used; } 194 | inline size_t capacity() { return limit; } 195 | inline size_t load() { return (used + tombs) * load_multiplier / limit; } 196 | inline size_t index_mask() { return limit - 1; } 197 | inline size_t hash_index(uint64_t h) { return h & index_mask(); } 198 | inline size_t key_index(Key key) { return hash_index(_hasher(key)); } 199 | inline hasher hash_function() const { return _hasher; } 200 | inline iterator begin() { return iterator{ this, 0 }; } 201 | inline iterator end() { return iterator{ this, limit }; } 202 | 203 | /* 204 | * bit manipulation helpers 205 | */ 206 | 207 | enum bitmap_state { 208 | available = 0, occupied = 1, deleted = 2, recycled = 3 209 | }; 210 | static inline size_t bitmap_capacity(size_t limit) 211 | { 212 | return (((limit + 3) >> 2) + 7) & ~7; 213 | } 214 | static inline size_t bitmap_idx(size_t i) { return i >> 5; } 215 | static inline size_t bitmap_shift(size_t i) { return ((i << 1) & 63); } 216 | static inline bitmap_state bitmap_get(uint64_t *bitmap, size_t i) 217 | { 218 | return (bitmap_state)((bitmap[bitmap_idx(i)] >> bitmap_shift(i)) & 3); 219 | } 220 | static inline void bitmap_set(uint64_t *bitmap, size_t i, uint64_t value) 221 | { 222 | bitmap[bitmap_idx(i)] |= (value << bitmap_shift(i)); 223 | } 224 | static inline void bitmap_clear(uint64_t *bitmap, size_t i, uint64_t value) 225 | { 226 | bitmap[bitmap_idx(i)] &= ~(value << bitmap_shift(i)); 227 | } 228 | static inline bool is_pow2(intptr_t n) { return ((n & -n) == n); } 229 | 230 | /** 231 | * the implementation 232 | */ 233 | 234 | void resize_internal(data_type *old_data, uint64_t *old_bitmap, 235 | size_t old_limit, size_t new_limit) 236 | { 237 | size_t data_size = sizeof(data_type) * new_limit; 238 | size_t bitmap_size = bitmap_capacity(new_limit); 239 | size_t total_size = data_size + bitmap_size; 240 | 241 | assert(is_pow2(new_limit)); 242 | 243 | data = (data_type*)malloc(total_size); 244 | bitmap = (uint64_t*)((char*)data + data_size); 245 | limit = new_limit; 246 | memset(bitmap, 0, bitmap_size); 247 | 248 | size_t i = 0; 249 | for (data_type *v = old_data; v != old_data + old_limit; v++, i++) { 250 | if ((bitmap_get(old_bitmap, i) & occupied) != occupied) continue; 251 | for (size_t j = key_index(v->first); ; j = (j+1) & index_mask()) { 252 | if ((bitmap_get(bitmap, j) & occupied) != occupied) { 253 | bitmap_set(bitmap, j, occupied); 254 | new (&data[j]) data_type(); 255 | data[j] = /* copy */ *v; 256 | break; 257 | } 258 | } 259 | } 260 | 261 | tombs = 0; 262 | for (size_t i = 0; i < old_limit; i++) { 263 | if ((bitmap_get(old_bitmap, i) & occupied) == occupied) { 264 | old_data[i].~data_type(); 265 | } 266 | } 267 | free(old_data); 268 | } 269 | 270 | void clear() 271 | { 272 | for (size_t i = 0; i < limit; i++) { 273 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 274 | data[i].~data_type(); 275 | } 276 | } 277 | size_t bitmap_size = bitmap_capacity(limit); 278 | memset(bitmap, 0, bitmap_size); 279 | used = tombs = 0; 280 | } 281 | 282 | iterator insert(const value_type& v) 283 | { 284 | for (size_t i = key_index(v); ; i = (i+1) & index_mask()) { 285 | bitmap_state state = bitmap_get(bitmap, i); 286 | if ((state & recycled) == available) { 287 | bitmap_set(bitmap, i, occupied); 288 | new (&data[i]) data_type(); 289 | data[i].first = /* copy */ v; 290 | used++; 291 | if ((state & deleted) == deleted) tombs--; 292 | if (load() > load_factor) { 293 | resize_internal(data, bitmap, limit, limit << 1); 294 | for (i = key_index(v); ; i = (i+1) & index_mask()) { 295 | bitmap_state state = bitmap_get(bitmap, i); 296 | if (state == available) abort(); 297 | else if (state == deleted); /* skip */ 298 | else if (_compare(data[i].first, v)) { 299 | return iterator{this, i}; 300 | } 301 | } 302 | } else { 303 | return iterator{this, i}; 304 | } 305 | } else if (_compare(data[i].first, v)) { 306 | return iterator{this, i}; 307 | } 308 | } 309 | } 310 | 311 | iterator find(const Key &key) 312 | { 313 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 314 | bitmap_state state = bitmap_get(bitmap, i); 315 | if (state == available) /* notfound */ break; 316 | else if (state == deleted); /* skip */ 317 | else if (_compare(data[i].first, key)) return iterator{this, i}; 318 | } 319 | return end(); 320 | } 321 | 322 | void erase(Key key) 323 | { 324 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 325 | bitmap_state state = bitmap_get(bitmap, i); 326 | if (state == available) /* notfound */ break; 327 | else if (state == deleted); /* skip */ 328 | else if (_compare(data[i].first, key)) { 329 | bitmap_set(bitmap, i, deleted); 330 | data[i].~data_type(); 331 | bitmap_clear(bitmap, i, occupied); 332 | used--; 333 | tombs++; 334 | return; 335 | } 336 | } 337 | } 338 | }; 339 | 340 | }; -------------------------------------------------------------------------------- /include/linked_hash_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Fast open addressing linked hash table with tombstone bit map. 3 | * 4 | * Copyright (c) 2020 Michael Clark 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | namespace ethical { 31 | 32 | /* 33 | * This open addressing linked_hash_map uses a 2-bit entry per slot 34 | * bitmap that eliminates the need for empty and deleted key sentinels. 35 | * The hashmap has a simple array of key and value pairs and the 36 | * tombstone bitmap, which are allocated in a single call to malloc. 37 | * 38 | * The linked_hash_map entries contains a bidirectional linked list 39 | * for predictable iteration order based on order of insertion. 40 | */ 41 | 42 | template , 44 | class Pred = std::equal_to> 45 | struct linked_hash_map 46 | { 47 | static const size_t default_size = (2<<3); /* 16 */ 48 | static const size_t load_factor = (2<<15); /* 0.5 */ 49 | static const size_t load_multiplier = (2<<16); /* 1.0 */ 50 | 51 | static inline Hash _hasher; 52 | static inline Pred _compare; 53 | 54 | struct data_type { 55 | Key first; 56 | Value second; 57 | Offset prev; 58 | Offset next; 59 | }; 60 | 61 | typedef Key key_type; 62 | typedef Value mapped_type; 63 | typedef std::pair value_type; 64 | typedef Hash hasher; 65 | typedef Pred key_equal; 66 | typedef Offset offset_type; 67 | typedef data_type& reference; 68 | typedef const data_type& const_reference; 69 | 70 | enum : offset_type { empty_offset = offset_type(-1) }; 71 | 72 | size_t used; 73 | size_t tombs; 74 | size_t limit; 75 | offset_type head; 76 | offset_type tail; 77 | data_type *data; 78 | uint64_t *bitmap; 79 | 80 | /* 81 | * scanning iterator 82 | */ 83 | 84 | struct iterator 85 | { 86 | linked_hash_map *h; 87 | size_t i; 88 | 89 | iterator& operator++() { i = h->data[i].next; return *this; } 90 | iterator operator++(int) { iterator r = *this; ++(*this); return r; } 91 | data_type& operator*() { return h->data[i]; } 92 | data_type* operator->() { return &h->data[i]; } 93 | bool operator==(const iterator &o) const { return h == o.h && i == o.i; } 94 | bool operator!=(const iterator &o) const { return h != o.h || i != o.i; } 95 | }; 96 | 97 | /* 98 | * constructors and destructor 99 | */ 100 | 101 | inline linked_hash_map() : linked_hash_map(default_size) {} 102 | inline linked_hash_map(size_t initial_size) : 103 | used(0), tombs(0), limit(initial_size), 104 | head(empty_offset), tail(empty_offset) 105 | { 106 | size_t data_size = sizeof(data_type) * limit; 107 | size_t bitmap_size = bitmap_capacity(limit); 108 | size_t total_size = data_size + bitmap_size; 109 | 110 | assert(is_pow2(initial_size)); 111 | 112 | data = (data_type*)malloc(total_size); 113 | bitmap = (uint64_t*)((char*)data + data_size); 114 | memset(bitmap, 0, bitmap_size); 115 | } 116 | 117 | inline ~linked_hash_map() 118 | { 119 | if (data) { 120 | for (size_t i = 0; i < limit; i++) { 121 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 122 | data[i].~data_type(); 123 | } 124 | } 125 | free(data); 126 | } 127 | } 128 | 129 | /* 130 | * copy constructor and assignment operator 131 | */ 132 | 133 | inline linked_hash_map(const linked_hash_map &o) : 134 | used(o.used), tombs(o.tombs), limit(o.limit), 135 | head(o.head), tail(o.tail) 136 | { 137 | size_t data_size = sizeof(data_type) * limit; 138 | size_t bitmap_size = bitmap_capacity(limit); 139 | size_t total_size = data_size + bitmap_size; 140 | 141 | data = (data_type*)malloc(total_size); 142 | bitmap = (uint64_t*)((char*)data + data_size); 143 | 144 | memcpy(bitmap, o.bitmap, bitmap_size); 145 | for (size_t i = 0; i < limit; i++) { 146 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 147 | data[i] = /* copy */ o.data[i]; 148 | } 149 | } 150 | } 151 | 152 | inline linked_hash_map(linked_hash_map &&o) : 153 | used(o.used), tombs(o.tombs), limit(o.limit), 154 | head(o.head), tail(o.tail), 155 | data(o.data), bitmap(o.bitmap) 156 | { 157 | o.data = nullptr; 158 | o.bitmap = nullptr; 159 | } 160 | 161 | inline linked_hash_map& operator=(const linked_hash_map &o) 162 | { 163 | free(data); 164 | 165 | used = o.used; 166 | tombs = o.tombs; 167 | limit = o.limit; 168 | head = o.head; 169 | tail = o.tail; 170 | 171 | size_t data_size = sizeof(data_type) * limit; 172 | size_t bitmap_size = bitmap_capacity(limit); 173 | size_t total_size = data_size + bitmap_size; 174 | 175 | data = (data_type*)malloc(total_size); 176 | bitmap = (uint64_t*)((char*)data + data_size); 177 | 178 | memcpy(bitmap, o.bitmap, bitmap_size); 179 | for (size_t i = 0; i < limit; i++) { 180 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 181 | data[i] = /* copy */ o.data[i]; 182 | } 183 | } 184 | 185 | return *this; 186 | } 187 | 188 | inline linked_hash_map& operator=(linked_hash_map &&o) 189 | { 190 | data = o.data; 191 | bitmap = o.bitmap; 192 | used = o.used; 193 | tombs = o.tombs; 194 | limit = o.limit; 195 | head = o.head; 196 | tail = o.tail; 197 | 198 | o.data = nullptr; 199 | o.bitmap = nullptr; 200 | 201 | return *this; 202 | } 203 | 204 | /* 205 | * member functions 206 | */ 207 | 208 | inline size_t size() { return used; } 209 | inline size_t capacity() { return limit; } 210 | inline size_t load() { return (used + tombs) * load_multiplier / limit; } 211 | inline size_t index_mask() { return limit - 1; } 212 | inline size_t hash_index(uint64_t h) { return h & index_mask(); } 213 | inline size_t key_index(Key key) { return hash_index(_hasher(key)); } 214 | inline hasher hash_function() const { return _hasher; } 215 | inline iterator begin() { return iterator{ this, size_t(head) }; } 216 | inline iterator end() { return iterator{ this, size_t(empty_offset) }; } 217 | 218 | /* 219 | * bit manipulation helpers 220 | */ 221 | 222 | enum bitmap_state { 223 | available = 0, occupied = 1, deleted = 2, recycled = 3 224 | }; 225 | static inline size_t bitmap_capacity(size_t limit) 226 | { 227 | return (((limit + 3) >> 2) + 7) & ~7; 228 | } 229 | static inline size_t bitmap_idx(size_t i) { return i >> 5; } 230 | static inline size_t bitmap_shift(size_t i) { return ((i << 1) & 63); } 231 | static inline bitmap_state bitmap_get(uint64_t *bitmap, size_t i) 232 | { 233 | return (bitmap_state)((bitmap[bitmap_idx(i)] >> bitmap_shift(i)) & 3); 234 | } 235 | static inline void bitmap_set(uint64_t *bitmap, size_t i, uint64_t value) 236 | { 237 | bitmap[bitmap_idx(i)] |= (value << bitmap_shift(i)); 238 | } 239 | static inline void bitmap_clear(uint64_t *bitmap, size_t i, uint64_t value) 240 | { 241 | bitmap[bitmap_idx(i)] &= ~(value << bitmap_shift(i)); 242 | } 243 | static inline bool is_pow2(intptr_t n) { return ((n & -n) == n); } 244 | 245 | /** 246 | * the implementation 247 | */ 248 | 249 | void resize_internal(data_type *old_data, uint64_t *old_bitmap, 250 | size_t old_limit, size_t new_limit) 251 | { 252 | size_t data_size = sizeof(data_type) * new_limit; 253 | size_t bitmap_size = bitmap_capacity(new_limit); 254 | size_t total_size = data_size + bitmap_size; 255 | 256 | assert(is_pow2(new_limit)); 257 | 258 | data = (data_type*)malloc(total_size); 259 | bitmap = (uint64_t*)((char*)data + data_size); 260 | limit = new_limit; 261 | memset(bitmap, 0, bitmap_size); 262 | 263 | offset_type k = empty_offset; 264 | for (size_t i = head; i != empty_offset; i = old_data[i].next) { 265 | data_type *v = old_data + i; 266 | for (size_t j = key_index(v->first); ; j = (j+1) & index_mask()) { 267 | if ((bitmap_get(bitmap, j) & occupied) != occupied) { 268 | bitmap_set(bitmap, j, occupied); 269 | if (i == head) head = (offset_type)j; 270 | if (i == tail) tail = (offset_type)j; 271 | data[j].first = /* copy */ v->first; 272 | data[j].second = /* copy */ v->second; 273 | data[j].next = empty_offset; 274 | if (k == empty_offset) { 275 | data[j].prev = empty_offset; 276 | } else { 277 | data[j].prev = (offset_type)k; 278 | data[k].next = (offset_type)j; 279 | } 280 | k = (offset_type)j; 281 | break; 282 | } 283 | } 284 | } 285 | 286 | tombs = 0; 287 | for (size_t i = 0; i < old_limit; i++) { 288 | if ((bitmap_get(old_bitmap, i) & occupied) == occupied) { 289 | old_data[i].~data_type(); 290 | } 291 | } 292 | free(old_data); 293 | } 294 | 295 | /* inserts indice link before specified position */ 296 | void insert_link_internal(offset_type pos, offset_type i) 297 | { 298 | if (head == tail && head == empty_offset) { 299 | head = tail = i; 300 | data[i].next = data[i].prev = empty_offset; 301 | } else if (pos == empty_offset) { 302 | data[i].next = empty_offset; 303 | data[i].prev = tail; 304 | data[tail].next = i; 305 | tail = i; 306 | } else { 307 | data[i].next = pos; 308 | data[i].prev = data[pos].prev; 309 | if (data[pos].prev != empty_offset) { 310 | data[data[pos].prev].next = i; 311 | } 312 | data[pos].prev = i; 313 | if (head == pos) head = i; 314 | } 315 | } 316 | 317 | /* remove indice link at the specified index */ 318 | void erase_link_internal(offset_type i) 319 | { 320 | assert(head != empty_offset && tail != empty_offset); 321 | if (head == tail && i == head) { 322 | head = tail = empty_offset; 323 | } else { 324 | if (head == i) head = data[i].next; 325 | if (tail == i) tail = data[i].prev; 326 | if (data[i].prev != empty_offset) { 327 | data[data[i].prev].next = data[i].next; 328 | } 329 | if (data[i].next != empty_offset) { 330 | data[data[i].next].prev = data[i].prev; 331 | } 332 | } 333 | } 334 | 335 | void clear() 336 | { 337 | for (size_t i = 0; i < limit; i++) { 338 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 339 | data[i].~data_type(); 340 | } 341 | } 342 | size_t bitmap_size = bitmap_capacity(limit); 343 | memset(bitmap, 0, bitmap_size); 344 | head = tail = empty_offset; 345 | used = tombs = 0; 346 | } 347 | 348 | iterator insert(const value_type& val) { return insert(end(), val); } 349 | iterator insert(Key key, Value val) { return insert(end(), value_type(key, val)); } 350 | 351 | iterator insert(iterator h, const value_type& v) 352 | { 353 | for (size_t i = key_index(v.first); ; i = (i+1) & index_mask()) { 354 | bitmap_state state = bitmap_get(bitmap, i); 355 | if ((state & recycled) == available) { 356 | bitmap_set(bitmap, i, occupied); 357 | data[i] = /* copy */ data_type{v.first, v.second}; 358 | insert_link_internal((offset_type)h.i, (offset_type)i); 359 | used++; 360 | if ((state & deleted) == deleted) tombs--; 361 | if (load() > load_factor) { 362 | resize_internal(data, bitmap, limit, limit << 1); 363 | for (i = key_index(v.first); ; i = (i+1) & index_mask()) { 364 | bitmap_state state = bitmap_get(bitmap, i); 365 | if (state == available) abort(); 366 | else if (state == deleted); /* skip */ 367 | else if (_compare(data[i].first, v.first)) { 368 | return iterator{this, i}; 369 | } 370 | } 371 | } else { 372 | return iterator{this, i}; 373 | } 374 | } else if (_compare(data[i].first, v.first)) { 375 | data[i].second = v.second; 376 | return iterator{this, i}; 377 | } 378 | } 379 | } 380 | 381 | Value& operator[](const Key &key) 382 | { 383 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 384 | bitmap_state state = bitmap_get(bitmap, i); 385 | if ((state & recycled) == available) { 386 | bitmap_set(bitmap, i, occupied); 387 | data[i].first = key; 388 | insert_link_internal(empty_offset, (offset_type)i); 389 | used++; 390 | if ((state & deleted) == deleted) tombs--; 391 | if (load() > load_factor) { 392 | resize_internal(data, bitmap, limit, limit << 1); 393 | for (i = key_index(key); ; i = (i+1) & index_mask()) { 394 | bitmap_state state = bitmap_get(bitmap, i); 395 | if (state == available) abort(); 396 | else if (state == deleted); /* skip */ 397 | else if (_compare(data[i].first, key)) { 398 | return data[i].second; 399 | } 400 | } 401 | } 402 | return data[i].second; 403 | } else if (_compare(data[i].first, key)) { 404 | return data[i].second; 405 | } 406 | } 407 | } 408 | 409 | iterator find(const Key &key) 410 | { 411 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 412 | bitmap_state state = bitmap_get(bitmap, i); 413 | if (state == available) /* notfound */ break; 414 | else if (state == deleted); /* skip */ 415 | else if (_compare(data[i].first, key)) return iterator{this, i}; 416 | } 417 | return end(); 418 | } 419 | 420 | void erase(Key key) 421 | { 422 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 423 | bitmap_state state = bitmap_get(bitmap, i); 424 | if (state == available) /* notfound */ break; 425 | else if (state == deleted); /* skip */ 426 | else if (_compare(data[i].first, key)) { 427 | bitmap_set(bitmap, i, deleted); 428 | data[i].~data_type(); 429 | bitmap_clear(bitmap, i, occupied); 430 | erase_link_internal((offset_type)i); 431 | used--; 432 | tombs++; 433 | return; 434 | } 435 | } 436 | } 437 | 438 | bool operator==(const linked_hash_map &o) const 439 | { 440 | auto i = const_cast(this)->begin(); 441 | auto j = const_cast(&o)->begin(); 442 | while (i != const_cast(this)->end() || j != const_cast(&o)->end()) { 443 | if (i == const_cast(this)->end() && j != const_cast(&o)->end()) return false; 444 | if (i != const_cast(this)->end() && j == const_cast(&o)->end()) return false; 445 | if (!(i->first == j->first && i->second == j->second)) return false; 446 | i++; 447 | j++; 448 | } 449 | return true; 450 | } 451 | 452 | bool operator!=(const linked_hash_map &o) const { return !(*this == o); } 453 | }; 454 | 455 | }; -------------------------------------------------------------------------------- /include/linked_hash_set.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Fast open addressing linked hash table with tombstone bit map. 3 | * 4 | * Copyright (c) 2020 Michael Clark 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | namespace ethical { 31 | 32 | /* 33 | * This open addressing linked_hash_set uses a 2-bit entry per slot 34 | * bitmap that eliminates the need for empty and deleted key sentinels. 35 | * The hashmap has a simple array of key and value pairs and the 36 | * tombstone bitmap, which are allocated in a single call to malloc. 37 | * 38 | * The linked_hash_set entries contains a bidirectional linked list 39 | * for predictable iteration order based on order of insertion. 40 | */ 41 | 42 | template , 44 | class Pred = std::equal_to> 45 | struct linked_hash_set 46 | { 47 | static const size_t default_size = (2<<3); /* 16 */ 48 | static const size_t load_factor = (2<<15); /* 0.5 */ 49 | static const size_t load_multiplier = (2<<16); /* 1.0 */ 50 | 51 | static inline Hash _hasher; 52 | static inline Pred _compare; 53 | 54 | struct data_type { 55 | Key first; 56 | Offset prev; 57 | Offset next; 58 | }; 59 | 60 | typedef Key value_type; 61 | typedef Hash hasher; 62 | typedef Pred key_equal; 63 | typedef Offset offset_type; 64 | typedef data_type& reference; 65 | typedef const data_type& const_reference; 66 | 67 | enum : offset_type { empty_offset = offset_type(-1) }; 68 | 69 | size_t used; 70 | size_t tombs; 71 | size_t limit; 72 | offset_type head; 73 | offset_type tail; 74 | data_type *data; 75 | uint64_t *bitmap; 76 | 77 | /* 78 | * scanning iterator 79 | */ 80 | 81 | struct iterator 82 | { 83 | linked_hash_set *h; 84 | size_t i; 85 | 86 | iterator& operator++() { i = h->data[i].next; return *this; } 87 | iterator operator++(int) { iterator r = *this; ++(*this); return r; } 88 | data_type& operator*() { return h->data[i]; } 89 | data_type* operator->() { return &h->data[i]; } 90 | bool operator==(const iterator &o) const { return h == o.h && i == o.i; } 91 | bool operator!=(const iterator &o) const { return h != o.h || i != o.i; } 92 | }; 93 | 94 | /* 95 | * constructors and destructor 96 | */ 97 | 98 | inline linked_hash_set() : linked_hash_set(default_size) {} 99 | inline linked_hash_set(size_t initial_size) : 100 | used(0), tombs(0), limit(initial_size), 101 | head(empty_offset), tail(empty_offset) 102 | { 103 | size_t data_size = sizeof(data_type) * limit; 104 | size_t bitmap_size = bitmap_capacity(limit); 105 | size_t total_size = data_size + bitmap_size; 106 | 107 | assert(is_pow2(initial_size)); 108 | 109 | data = (data_type*)malloc(total_size); 110 | bitmap = (uint64_t*)((char*)data + data_size); 111 | memset(bitmap, 0, bitmap_size); 112 | } 113 | 114 | inline ~linked_hash_set() 115 | { 116 | if (data) { 117 | for (size_t i = 0; i < limit; i++) { 118 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 119 | data[i].~data_type(); 120 | } 121 | } 122 | free(data); 123 | } 124 | } 125 | 126 | /* 127 | * copy constructor and assignment operator 128 | */ 129 | 130 | inline linked_hash_set(const linked_hash_set &o) : 131 | used(o.used), tombs(o.tombs), limit(o.limit), 132 | head(o.head), tail(o.tail) 133 | { 134 | size_t data_size = sizeof(data_type) * limit; 135 | size_t bitmap_size = bitmap_capacity(limit); 136 | size_t total_size = data_size + bitmap_size; 137 | 138 | data = (data_type*)malloc(total_size); 139 | bitmap = (uint64_t*)((char*)data + data_size); 140 | 141 | memcpy(bitmap, o.bitmap, bitmap_size); 142 | for (size_t i = 0; i < limit; i++) { 143 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 144 | data[i] = /* copy */ o.data[i]; 145 | } 146 | } 147 | } 148 | 149 | inline linked_hash_set(linked_hash_set &&o) : 150 | used(o.used), tombs(o.tombs), limit(o.limit), 151 | head(o.head), tail(o.tail), 152 | data(o.data), bitmap(o.bitmap) 153 | { 154 | o.data = nullptr; 155 | o.bitmap = nullptr; 156 | } 157 | 158 | inline linked_hash_set& operator=(const linked_hash_set &o) 159 | { 160 | free(data); 161 | 162 | used = o.used; 163 | tombs = o.tombs; 164 | limit = o.limit; 165 | head = o.head; 166 | tail = o.tail; 167 | 168 | size_t data_size = sizeof(data_type) * limit; 169 | size_t bitmap_size = bitmap_capacity(limit); 170 | size_t total_size = data_size + bitmap_size; 171 | 172 | data = (data_type*)malloc(total_size); 173 | bitmap = (uint64_t*)((char*)data + data_size); 174 | 175 | memcpy(bitmap, o.bitmap, bitmap_size); 176 | for (size_t i = 0; i < limit; i++) { 177 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 178 | data[i] = /* copy */ o.data[i]; 179 | } 180 | } 181 | 182 | return *this; 183 | } 184 | 185 | inline linked_hash_set& operator=(linked_hash_set &&o) 186 | { 187 | data = o.data; 188 | bitmap = o.bitmap; 189 | used = o.used; 190 | tombs = o.tombs; 191 | limit = o.limit; 192 | head = o.head; 193 | tail = o.tail; 194 | 195 | o.data = nullptr; 196 | o.bitmap = nullptr; 197 | 198 | return *this; 199 | } 200 | 201 | /* 202 | * member functions 203 | */ 204 | 205 | inline size_t size() { return used; } 206 | inline size_t capacity() { return limit; } 207 | inline size_t load() { return (used + tombs) * load_multiplier / limit; } 208 | inline size_t index_mask() { return limit - 1; } 209 | inline size_t hash_index(uint64_t h) { return h & index_mask(); } 210 | inline size_t key_index(Key key) { return hash_index(_hasher(key)); } 211 | inline hasher hash_function() const { return _hasher; } 212 | inline iterator begin() { return iterator{ this, size_t(head) }; } 213 | inline iterator end() { return iterator{ this, size_t(empty_offset) }; } 214 | 215 | /* 216 | * bit manipulation helpers 217 | */ 218 | 219 | enum bitmap_state { 220 | available = 0, occupied = 1, deleted = 2, recycled = 3 221 | }; 222 | static inline size_t bitmap_capacity(size_t limit) 223 | { 224 | return (((limit + 3) >> 2) + 7) & ~7; 225 | } 226 | static inline size_t bitmap_idx(size_t i) { return i >> 5; } 227 | static inline size_t bitmap_shift(size_t i) { return ((i << 1) & 63); } 228 | static inline bitmap_state bitmap_get(uint64_t *bitmap, size_t i) 229 | { 230 | return (bitmap_state)((bitmap[bitmap_idx(i)] >> bitmap_shift(i)) & 3); 231 | } 232 | static inline void bitmap_set(uint64_t *bitmap, size_t i, uint64_t value) 233 | { 234 | bitmap[bitmap_idx(i)] |= (value << bitmap_shift(i)); 235 | } 236 | static inline void bitmap_clear(uint64_t *bitmap, size_t i, uint64_t value) 237 | { 238 | bitmap[bitmap_idx(i)] &= ~(value << bitmap_shift(i)); 239 | } 240 | static inline bool is_pow2(intptr_t n) { return ((n & -n) == n); } 241 | 242 | /** 243 | * the implementation 244 | */ 245 | 246 | void resize_internal(data_type *old_data, uint64_t *old_bitmap, 247 | size_t old_limit, size_t new_limit) 248 | { 249 | size_t data_size = sizeof(data_type) * new_limit; 250 | size_t bitmap_size = bitmap_capacity(new_limit); 251 | size_t total_size = data_size + bitmap_size; 252 | 253 | assert(is_pow2(new_limit)); 254 | 255 | data = (data_type*)malloc(total_size); 256 | bitmap = (uint64_t*)((char*)data + data_size); 257 | limit = new_limit; 258 | memset(bitmap, 0, bitmap_size); 259 | 260 | offset_type k = empty_offset; 261 | for (size_t i = head; i != empty_offset; i = old_data[i].next) { 262 | data_type *v = old_data + i; 263 | for (size_t j = key_index(v->first); ; j = (j+1) & index_mask()) { 264 | if ((bitmap_get(bitmap, j) & occupied) != occupied) { 265 | bitmap_set(bitmap, j, occupied); 266 | if (i == head) head = (offset_type)j; 267 | if (i == tail) tail = (offset_type)j; 268 | data[j].first = /* copy */ v->first; 269 | data[j].next = empty_offset; 270 | if (k == empty_offset) { 271 | data[j].prev = empty_offset; 272 | } else { 273 | data[j].prev = (offset_type)k; 274 | data[k].next = (offset_type)j; 275 | } 276 | k = (offset_type)j; 277 | break; 278 | } 279 | } 280 | } 281 | 282 | tombs = 0; 283 | for (size_t i = 0; i < old_limit; i++) { 284 | if ((bitmap_get(old_bitmap, i) & occupied) == occupied) { 285 | old_data[i].~data_type(); 286 | } 287 | } 288 | free(old_data); 289 | } 290 | 291 | /* inserts indice link before specified position */ 292 | void insert_link_internal(offset_type pos, offset_type i) 293 | { 294 | if (head == tail && head == empty_offset) { 295 | head = tail = i; 296 | data[i].next = data[i].prev = empty_offset; 297 | } else if (pos == empty_offset) { 298 | data[i].next = empty_offset; 299 | data[i].prev = tail; 300 | data[tail].next = i; 301 | tail = i; 302 | } else { 303 | data[i].next = pos; 304 | data[i].prev = data[pos].prev; 305 | if (data[pos].prev != empty_offset) { 306 | data[data[pos].prev].next = i; 307 | } 308 | data[pos].prev = i; 309 | if (head == pos) head = i; 310 | } 311 | } 312 | 313 | /* remove indice link at the specified index */ 314 | void erase_link_internal(offset_type i) 315 | { 316 | assert(head != empty_offset && tail != empty_offset); 317 | if (head == tail && i == head) { 318 | head = tail = empty_offset; 319 | } else { 320 | if (head == i) head = data[i].next; 321 | if (tail == i) tail = data[i].prev; 322 | if (data[i].prev != empty_offset) { 323 | data[data[i].prev].next = data[i].next; 324 | } 325 | if (data[i].next != empty_offset) { 326 | data[data[i].next].prev = data[i].prev; 327 | } 328 | } 329 | } 330 | 331 | void clear() 332 | { 333 | for (size_t i = 0; i < limit; i++) { 334 | if ((bitmap_get(bitmap, i) & occupied) == occupied) { 335 | data[i].~data_type(); 336 | } 337 | } 338 | size_t bitmap_size = bitmap_capacity(limit); 339 | memset(bitmap, 0, bitmap_size); 340 | head = tail = empty_offset; 341 | used = tombs = 0; 342 | } 343 | 344 | iterator insert(const value_type& val) { return insert(end(), val); } 345 | 346 | iterator insert(iterator h, const value_type& v) 347 | { 348 | for (size_t i = key_index(v); ; i = (i+1) & index_mask()) { 349 | bitmap_state state = bitmap_get(bitmap, i); 350 | if ((state & recycled) == available) { 351 | bitmap_set(bitmap, i, occupied); 352 | data[i].first = /* copy */ v; 353 | insert_link_internal((offset_type)h.i, (offset_type)i); 354 | used++; 355 | if ((state & deleted) == deleted) tombs--; 356 | if (load() > load_factor) { 357 | resize_internal(data, bitmap, limit, limit << 1); 358 | for (i = key_index(v); ; i = (i+1) & index_mask()) { 359 | bitmap_state state = bitmap_get(bitmap, i); 360 | if (state == available) abort(); 361 | else if (state == deleted); /* skip */ 362 | else if (_compare(data[i].first, v)) { 363 | return iterator{this, i}; 364 | } 365 | } 366 | } else { 367 | return iterator{this, i}; 368 | } 369 | } else if (_compare(data[i].first, v)) { 370 | return iterator{this, i}; 371 | } 372 | } 373 | } 374 | 375 | iterator find(const Key &key) 376 | { 377 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 378 | bitmap_state state = bitmap_get(bitmap, i); 379 | if (state == available) /* notfound */ break; 380 | else if (state == deleted); /* skip */ 381 | else if (_compare(data[i].first, key)) return iterator{this, i}; 382 | } 383 | return end(); 384 | } 385 | 386 | void erase(Key key) 387 | { 388 | for (size_t i = key_index(key); ; i = (i+1) & index_mask()) { 389 | bitmap_state state = bitmap_get(bitmap, i); 390 | if (state == available) /* notfound */ break; 391 | else if (state == deleted); /* skip */ 392 | else if (_compare(data[i].first, key)) { 393 | bitmap_set(bitmap, i, deleted); 394 | data[i].~data_type(); 395 | bitmap_clear(bitmap, i, occupied); 396 | erase_link_internal((offset_type)i); 397 | used--; 398 | tombs++; 399 | return; 400 | } 401 | } 402 | } 403 | }; 404 | 405 | }; -------------------------------------------------------------------------------- /tests/bench_map.cc: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "hash_map.h" 12 | #include "linked_hash_map.h" 13 | 14 | using namespace std::chrono; 15 | 16 | #ifndef _WIN32 17 | std::string get_proc_info(const char *key) 18 | { 19 | FILE *file; 20 | char buf[1024]; 21 | char *line; 22 | 23 | if (!(file = fopen("/proc/cpuinfo", "r"))) { 24 | return ""; 25 | } 26 | 27 | while ((line = fgets(buf, sizeof(buf), file)) != nullptr) { 28 | std::string s(line); 29 | if (s.find(key) != std::string::npos) { 30 | size_t c = s.find(":"); 31 | return s.substr(c + 2, s.size() - c - 3); 32 | } 33 | } 34 | 35 | fclose(file); 36 | return ""; 37 | } 38 | 39 | std::string get_cpu_model() 40 | { 41 | std::string s = get_proc_info("model name"); 42 | size_t c = s.find("@"); 43 | return c != std::string::npos ? s.substr(0, c-1) : s; 44 | } 45 | #endif 46 | 47 | struct rng 48 | { 49 | std::random_device random_device; 50 | std::default_random_engine random_engine; 51 | std::uniform_int_distribution random_dist; 52 | 53 | template 54 | T get() { return random_dist(random_engine); } 55 | }; 56 | 57 | template 58 | std::vector> get_random(size_t count) 59 | { 60 | rng r; 61 | std::vector> data; 62 | ethical::hash_map set; 63 | for (size_t i = 0; i < count; i++) { 64 | K key = r.get(); 65 | while (set[key] == 1) key = r.get(); 66 | V val = r.get(); 67 | data.push_back(std::pair(key, val)); 68 | set[key] = 1; 69 | }; 70 | return data; 71 | } 72 | 73 | template 74 | void print_timings(const char *name, T t1, T t2, T t3, T t4, T t5, T t6, size_t count) 75 | { 76 | char buf[128]; 77 | snprintf(buf, sizeof(buf), "_%s::insert_", name); 78 | printf("|%-40s|%8s|%12zu|%8.1f|\n", buf, "random", count, 79 | duration_cast(t2-t1).count()/(float)count); 80 | snprintf(buf, sizeof(buf), "_%s::clear_", name); 81 | printf("|%-40s|%8s|%12zu|%8.1f|\n", buf, "random", count, 82 | duration_cast(t3-t2).count()/(float)count); 83 | snprintf(buf, sizeof(buf), "_%s::insert_", name); 84 | printf("|%-40s|%8s|%12zu|%8.1f|\n", buf, "random", count, 85 | duration_cast(t4-t3).count()/(float)count); 86 | snprintf(buf, sizeof(buf), "_%s::lookup_", name); 87 | printf("|%-40s|%8s|%12zu|%8.1f|\n", buf, "random", count, 88 | duration_cast(t5-t4).count()/(float)count); 89 | snprintf(buf, sizeof(buf), "_%s::erase_", name); 90 | printf("|%-40s|%8s|%12zu|%8.1f|\n", buf, "random", count, 91 | duration_cast(t6-t5).count()/(float)count); 92 | printf("|%-40s|%8s|%12s|%8s|\n", "-", "-", "-", "-"); 93 | } 94 | 95 | static const size_t sizes[] = { 1023, 16383, 65535, 1048575, 0 }; 96 | 97 | template 98 | void bench_spread(const char *name, size_t count, size_t spread) 99 | { 100 | Map map; 101 | auto t1 = system_clock::now(); 102 | for (size_t i = 0; i < count; i++) { 103 | map[i&spread]++; 104 | } 105 | auto t2 = system_clock::now(); 106 | char buf[128]; 107 | snprintf(buf, sizeof(buf), "_%s_", name); 108 | printf("|%-40s|%8zu|%12zu|%8.1f|\n", buf, spread, count, 109 | duration_cast(t2-t1).count()/(float)count); 110 | } 111 | 112 | template 113 | void bench_spread(const char *name, size_t count) 114 | { 115 | for (const size_t *s = sizes; *s != 0; s++) { 116 | bench_spread(name,count,*s); 117 | } 118 | printf("|%-40s|%8s|%12s|%8s|\n", "-", "-", "-", "-"); 119 | } 120 | 121 | template 122 | void bench_map(const char* name, size_t count) 123 | { 124 | typedef std::pair pair_type; 125 | 126 | Map ht; 127 | 128 | auto data = get_random(count); 129 | auto t1 = system_clock::now(); 130 | for (auto &ent : data) { 131 | ht.insert(ht.end(), pair_type(ent.first, ent.second)); 132 | } 133 | auto t2 = system_clock::now(); 134 | ht.clear(); 135 | auto t3 = system_clock::now(); 136 | for (auto &ent : data) { 137 | ht.insert(ht.end(), pair_type(ent.first, ent.second)); 138 | } 139 | auto t4 = system_clock::now(); 140 | for (auto &ent : data) { 141 | assert(ht.find(ent.first)->second == ent.second); 142 | } 143 | auto t5 = system_clock::now(); 144 | for (auto &ent : data) { 145 | ht.erase(ent.first); 146 | } 147 | auto t6 = system_clock::now(); 148 | 149 | print_timings(name, t1, t2, t3, t4, t5, t6, count); 150 | } 151 | 152 | void heading() 153 | { 154 | printf("\n"); 155 | printf("|%-40s|%8s|%12s|%8s|\n", 156 | "container", "spread", "count", "time_ns"); 157 | printf("|%-40s|%8s|%12s|%8s|\n", 158 | ":--------------------------------------", 159 | "-----:", "----:", "------:"); 160 | } 161 | 162 | int main(int argc, char **argv) 163 | { 164 | size_t count = 1000000; 165 | 166 | if (argc == 2) { 167 | count = atoi(argv[1]); 168 | } 169 | 170 | #ifndef _WIN32 171 | printf("cpu_model: %s\n", get_cpu_model().c_str()); 172 | #endif 173 | 174 | heading(); 175 | bench_spread>("ethical::hash_map::operator[]",count); 176 | bench_spread>("ethical::linked_hash_map::operator[]",count); 177 | 178 | heading(); 179 | bench_map>("ethical::hash_map", count); 180 | bench_map>("ethical::linked_hash_map", count); 181 | } -------------------------------------------------------------------------------- /tests/bytes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | static inline uint16_t be16(uint16_t v) 6 | { 7 | typedef uint8_t u8; 8 | union { uint8_t a[2]; uint16_t b; } u = { 9 | .a = { (u8)(v >> 8), (u8)(v) } 10 | }; 11 | return u.b; 12 | } 13 | 14 | static inline uint16_t le16(uint16_t v) 15 | { 16 | typedef uint8_t u8; 17 | union { uint8_t a[2]; uint16_t b; } u = { 18 | .a = { (u8)(v), (u8)(v >> 8) } 19 | }; 20 | return u.b; 21 | } 22 | 23 | static inline uint32_t be32(uint32_t v) 24 | { 25 | typedef uint8_t u8; 26 | union { uint8_t a[4]; uint32_t b; } u = { 27 | .a = { (u8)(v >> 24), (u8)(v >> 16), (u8)(v >> 8), (u8)(v) } 28 | }; 29 | return u.b; 30 | } 31 | 32 | static inline uint32_t le32(uint32_t v) 33 | { 34 | typedef uint8_t u8; 35 | union { uint8_t a[4]; uint32_t b; } u = { 36 | .a = { (u8)(v), (u8)(v >> 8), (u8)(v >> 16), (u8)(v >> 24) } 37 | }; 38 | return u.b; 39 | } 40 | 41 | static inline uint64_t be64(uint64_t v) 42 | { 43 | typedef uint8_t u8; 44 | union { uint8_t a[8]; uint64_t b; } u = { 45 | .a = { (u8)(v >> 56), (u8)(v >> 48), (u8)(v >> 40), (u8)(v >> 32), 46 | (u8)(v >> 24), (u8)(v >> 16), (u8)(v >> 8), (u8)(v) } 47 | }; 48 | return u.b; 49 | } 50 | 51 | static inline uint64_t le64(uint64_t v) 52 | { 53 | typedef uint8_t u8; 54 | union { uint8_t a[8]; uint64_t b; } u = { 55 | .a = { (u8)(v), (u8)(v >> 8), (u8)(v >> 16), (u8)(v >> 24), 56 | (u8)(v >> 32), (u8)(v >> 40), (u8)(v >> 48), (u8)(v >> 56) } 57 | }; 58 | return u.b; 59 | } 60 | 61 | #undef htobe16 62 | #undef htole16 63 | #undef be16toh 64 | #undef le16toh 65 | 66 | #undef htobe32 67 | #undef htole32 68 | #undef be32toh 69 | #undef le32toh 70 | 71 | #undef htobe64 72 | #undef htole64 73 | #undef be64toh 74 | #undef le64toh 75 | 76 | #define htobe16(x) be16(x) 77 | #define htole16(x) le16(x) 78 | #define be16toh(x) be16(x) 79 | #define le16toh(x) le16(x) 80 | 81 | #define htobe32(x) be32(x) 82 | #define htole32(x) le32(x) 83 | #define be32toh(x) be32(x) 84 | #define le32toh(x) le32(x) 85 | 86 | #define htobe64(x) be64(x) 87 | #define htole64(x) le64(x) 88 | #define be64toh(x) be64(x) 89 | #define le64toh(x) le64(x) 90 | -------------------------------------------------------------------------------- /tests/sha256.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Stanford University. 3 | * Copyright (c) 2014 Cryptography Research, Inc. 4 | * Released under the MIT License. 5 | */ 6 | 7 | #include 8 | 9 | #include "bytes.h" 10 | #include "sha256.h" 11 | 12 | static const uint32_t sha256_init_state[8] = { 13 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 14 | 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 15 | }; 16 | 17 | static const uint32_t sha256_k[64] = { 18 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 19 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 20 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 21 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 22 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 23 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 24 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 25 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 26 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 27 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 28 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 29 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 30 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 31 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 32 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 33 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 34 | }; 35 | 36 | static inline uint32_t ror(uint32_t x, int d) 37 | { 38 | return (x >> d) | (x << (32-d)); 39 | } 40 | 41 | static inline uint32_t sigma0(uint32_t h1) 42 | { 43 | return ror(h1, 2) ^ ror(h1, 13) ^ ror(h1, 22); 44 | } 45 | 46 | static inline uint32_t sigma1(uint32_t h4) 47 | { 48 | return ror(h4, 6) ^ ror(h4, 11) ^ ror(h4, 25); 49 | } 50 | 51 | static inline uint32_t gamma0(uint32_t a) 52 | { 53 | return ror(a, 7) ^ ror(a, 18) ^ (a >> 3); 54 | } 55 | 56 | static inline uint32_t gamma1(uint32_t b) 57 | { 58 | return ror(b, 17) ^ ror(b, 19) ^ (b >> 10); 59 | } 60 | 61 | static inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z) 62 | { 63 | return z ^ (x & (y ^ z)); 64 | } 65 | 66 | static inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z) 67 | { 68 | return (x & y) ^ ((x ^ y) & z); 69 | } 70 | 71 | static void sha256_transform(sha256_ctx *ctx, const unsigned char *buf) 72 | { 73 | uint32_t H[8], W[64], T0, T1; 74 | size_t i; 75 | 76 | for (i = 0; i < 8; i++) { 77 | H[i] = ctx->chain[i]; 78 | } 79 | 80 | for (i=0; i<16; i++, buf += sizeof(uint32_t)) { 81 | W[i] = htobe32(*((uint32_t*)buf)); 82 | } 83 | 84 | for (; i<64; i++) { 85 | W[i] = gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16]; 86 | } 87 | 88 | for (i=0; i<64; i++) { 89 | T0 = W[i] + H[7] + sigma1(H[4]) + ch(H[4], H[5], H[6]) + sha256_k[i]; 90 | T1 = maj(H[0], H[1], H[2]) + sigma0(H[0]); 91 | H[7] = H[6]; 92 | H[6] = H[5]; 93 | H[5] = H[4]; 94 | H[4] = H[3] + T0; 95 | H[3] = H[2]; 96 | H[2] = H[1]; 97 | H[1] = H[0]; 98 | H[0] = T0 + T1; 99 | } 100 | 101 | for (i = 0; i < 8; i++) { 102 | ctx->chain[i] += H[i]; 103 | } 104 | } 105 | 106 | void sha256_init(sha256_ctx *ctx) 107 | { 108 | ctx->nbytes = 0; 109 | ctx->digestlen = sha256_hash_size; 110 | memcpy(ctx->chain, sha256_init_state, sizeof(sha256_init_state)); 111 | memset(ctx->block, 0, sizeof(ctx->block)); 112 | } 113 | 114 | void sha256_update(sha256_ctx *ctx, const void *data, size_t len) 115 | { 116 | while (len) { 117 | uint64_t fill = ctx->nbytes % 64, accept = 64 - fill; 118 | if (accept > len) { 119 | accept = len; 120 | } 121 | ctx->nbytes += accept; 122 | memcpy(ctx->block + fill, data, accept); 123 | 124 | if (fill+accept == 64) { 125 | sha256_transform(ctx, ctx->block); 126 | } 127 | 128 | len -= accept; 129 | data = ((const char *)data + accept); 130 | } 131 | } 132 | 133 | void sha256_final(sha256_ctx *ctx, unsigned char *result) 134 | { 135 | uint64_t fill = ctx->nbytes % 64, i; 136 | ctx->block[fill++] = 0x80; 137 | if (fill > 48) { 138 | memset(ctx->block + fill, 0, 64-fill); 139 | sha256_transform(ctx, ctx->block); 140 | fill = 0; 141 | } 142 | memset(ctx->block + fill, 0, 48-fill); 143 | 144 | uint64_t highCount = 0, lowCount = htobe64((ctx->nbytes * 8)); 145 | memcpy(&ctx->block[48],&highCount,8); 146 | memcpy(&ctx->block[56],&lowCount,8); 147 | sha256_transform(ctx, ctx->block); 148 | for (i=0; i<8; i++) { 149 | ctx->chain[i] = htobe32(ctx->chain[i]); 150 | } 151 | memcpy(result, ctx->chain, ctx->digestlen); 152 | } -------------------------------------------------------------------------------- /tests/sha256.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define sha256_block_size 64 11 | #define sha256_hash_size 32 12 | 13 | struct sha256_ctx { 14 | uint32_t chain[8]; 15 | uint8_t block[sha256_block_size]; 16 | uint64_t nbytes; 17 | uint64_t digestlen; 18 | }; 19 | 20 | typedef struct sha256_ctx sha256_ctx; 21 | 22 | void sha256_init(sha256_ctx *ctx); 23 | void sha256_256_init(sha256_ctx *ctx); 24 | void sha256_update(sha256_ctx *ctx, const void *data, size_t len); 25 | void sha256_final(sha256_ctx *ctx, unsigned char *result); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif -------------------------------------------------------------------------------- /tests/test_hash_map.cc: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "hash_map.h" 13 | 14 | using namespace std::chrono; 15 | 16 | typedef std::pair number_pair_t; 17 | 18 | static const number_pair_t numbers[] = { 19 | { 7, 1 }, { 11, 2 }, { 15, 3 }, { 19, 4 }, { 21, 5 }, { 0, 0 } 20 | }; 21 | 22 | void test_hash_map_simple() 23 | { 24 | ethical::hash_map ht; 25 | 26 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 27 | ht.insert(n->first, n->second); 28 | } 29 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 30 | assert(ht.find(n->first)->second == n->second); 31 | } 32 | for (auto ent : ht) { 33 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 34 | if (ent.first == n->first) { 35 | assert(ent.second == n->second); 36 | } 37 | } 38 | } 39 | } 40 | 41 | void test_hash_map_delete() 42 | { 43 | ethical::hash_map ht; 44 | ht.insert(7, 8); 45 | assert(ht.find(7)->second == 8); 46 | ht.erase(7); 47 | assert(ht.find(7) == ht.end()); 48 | } 49 | 50 | void test_hash_map_copy() 51 | { 52 | static const number_pair_t numbers[] = { 53 | { 666, 4 }, { 777, 1 }, { 888, 2 }, {999, 3}, {0, 0} 54 | }; 55 | 56 | ethical::hash_map hs, ht; 57 | size_t count; 58 | 59 | ht.insert({666, 4}); 60 | ht.insert({777, 1}); 61 | ht.insert({888, 2}); 62 | ht.insert({999, 3}); 63 | 64 | hs = ht; 65 | 66 | count = 0; 67 | for (auto ent : hs) { 68 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 69 | if (ent.first == n->first) { 70 | assert(ent.second == n->second); 71 | count++; 72 | } 73 | } 74 | } 75 | assert(count == 4); 76 | 77 | ethical::hash_map hu(hs); 78 | 79 | count = 0; 80 | for (auto ent : hu) { 81 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 82 | if (ent.first == n->first) { 83 | assert(ent.second == n->second); 84 | count++; 85 | } 86 | } 87 | } 88 | assert(count == 4); 89 | } 90 | 91 | void test_hash_map_move() 92 | { 93 | static const number_pair_t numbers[] = { 94 | { 666, 4 }, { 777, 1 }, { 888, 2 }, {999, 3}, {0, 0} 95 | }; 96 | 97 | ethical::hash_map ht = std::move(ethical::hash_map()); 98 | size_t count; 99 | 100 | ht.insert({666, 4}); 101 | ht.insert({777, 1}); 102 | ht.insert({888, 2}); 103 | ht.insert({999, 3}); 104 | 105 | count = 0; 106 | for (auto ent : ht) { 107 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 108 | if (ent.first == n->first) { 109 | assert(ent.second == n->second); 110 | count++; 111 | } 112 | } 113 | } 114 | assert(count == 4); 115 | } 116 | 117 | void test_hash_map_noloop() 118 | { 119 | ethical::hash_map h(4); 120 | 121 | // Fill the table with tombstones. 122 | for (int i = 0; i < 4; i++) { 123 | h[i]; 124 | h.erase(i); 125 | } 126 | 127 | // Infinite loop. 128 | h.find(0); 129 | } 130 | 131 | template 132 | void insert_random(size_t limit, F fn) 133 | { 134 | std::random_device random_device; 135 | std::default_random_engine random_engine; 136 | std::uniform_int_distribution random_dist; 137 | 138 | for (size_t i = 0; i < limit; i++) { 139 | uint64_t key = random_dist(random_engine); 140 | uint64_t val = random_dist(random_engine); 141 | fn(key, val); 142 | } 143 | } 144 | 145 | void test_hash_map_random(size_t limit) 146 | { 147 | ethical::hash_map ht; 148 | std::map hm; 149 | 150 | insert_random(limit, [&](size_t key, size_t val) { 151 | ht.insert(key, val); 152 | hm.insert(hm.end(), std::pair(key, val)); 153 | }); 154 | 155 | for (auto &ent : hm) { 156 | assert(ht.find(ent.first)->second == ent.second); 157 | } 158 | } 159 | 160 | int main(int argc, char **argv) 161 | { 162 | test_hash_map_simple(); 163 | test_hash_map_delete(); 164 | test_hash_map_noloop(); 165 | test_hash_map_random(1<<16); 166 | test_hash_map_copy(); 167 | test_hash_map_move(); 168 | return 0; 169 | } -------------------------------------------------------------------------------- /tests/test_hash_set.cc: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "sha256.h" 12 | #include "hash_map.h" 13 | #include "hash_set.h" 14 | 15 | struct hash_hash_map 16 | { 17 | typedef ethical::hash_map hash_map_t; 18 | 19 | size_t operator()(hash_map_t &m) 20 | { 21 | uint8_t b[32]; 22 | sha256_ctx ctx; 23 | sha256_init(&ctx); 24 | for (auto &ent : m) { 25 | sha256_update(&ctx, (const void*)&ent.first, sizeof(ent.first)); 26 | sha256_update(&ctx, (const void*)&ent.second, sizeof(ent.second)); 27 | } 28 | sha256_final(&ctx, b); 29 | return *(size_t*)b; 30 | } 31 | }; 32 | 33 | void test_hash_set_simple() 34 | { 35 | static const uintptr_t numbers[] = { 8, 9, 6, 7, 4, 5, 2, 3, 0 }; 36 | 37 | ethical::hash_set ht; 38 | 39 | for (const uintptr_t *n = numbers; *n != 0; n++) { 40 | ht.insert(*n); 41 | } 42 | for (const uintptr_t *n = numbers; *n != 0; n++) { 43 | assert(ht.find(*n)->first == *n); 44 | } 45 | const uintptr_t *n = numbers; 46 | for (uintptr_t n : numbers) { 47 | if (n > 0) assert(ht.find(n) != ht.end()); 48 | } 49 | } 50 | 51 | ethical::hash_map 52 | make_map(std::initializer_list> l) 53 | { 54 | ethical::hash_map m; 55 | for (auto i = l.begin(); i != l.end(); i++) { 56 | m.insert(i->first, i->second); 57 | } 58 | return std::move(m); 59 | } 60 | 61 | typedef ethical::hash_map hash_map_t; 62 | typedef ethical::hash_set hash_set_t; 63 | 64 | void test_hash_set_hash_map() 65 | { 66 | hash_set_t s; 67 | s.insert(make_map({{1,2},{3,4},{5,6}})); 68 | s.insert(make_map({{1,2},{3,4},{5,6},{7,8}})); 69 | s.insert(make_map({{1,2},{3,4},{5,6},{7,8},{9,10}})); 70 | 71 | for (auto &h : s) { 72 | for (auto i = h.first.begin(); i != h.first.end(); i++) { 73 | if (i != h.first.begin()) printf(","); 74 | printf("%d=%d", i->first, i->second); 75 | } 76 | printf("\n"); 77 | } 78 | } 79 | 80 | int main(int argc, char **argv) 81 | { 82 | test_hash_set_simple(); 83 | test_hash_set_hash_map(); 84 | return 0; 85 | } -------------------------------------------------------------------------------- /tests/test_linked_hash_map.cc: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "linked_hash_map.h" 13 | 14 | using namespace std::chrono; 15 | 16 | typedef std::pair number_pair_t; 17 | 18 | static const number_pair_t numbers[] = { 19 | { 7, 1 }, { 11, 2 }, { 15, 3 }, { 19, 4 }, { 21, 5 }, { 0, 0} 20 | }; 21 | 22 | void test_linked_hash_map_simple() 23 | { 24 | ethical::linked_hash_map ht; 25 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 26 | ht.insert(n->first, n->second); 27 | } 28 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 29 | assert(ht.find(n->first)->second == n->second); 30 | } 31 | for (auto ent : ht) { 32 | for (const number_pair_t *n = numbers; n->first != 0; n++) { 33 | if (ent.first == n->first) { 34 | assert(ent.second == n->second); 35 | } 36 | } 37 | } 38 | } 39 | 40 | void test_linked_hash_map_insert() 41 | { 42 | ethical::linked_hash_map ht; 43 | 44 | auto i1 = ht.insert({777, 1}); 45 | auto i2 = ht.insert(i1, {888, 2}); 46 | auto i3 = ht.insert(i1, {999, 3}); 47 | auto i4 = ht.insert(i2, {666, 4}); 48 | 49 | static const number_pair_t numbers[] = { 50 | { 666, 4 }, { 888, 2 }, {999, 3}, { 777, 1 } 51 | }; 52 | 53 | size_t i = 0; 54 | for (auto ent : ht) { 55 | const number_pair_t *n = numbers + i++; 56 | assert(ent.first == n->first); 57 | assert(ent.second == n->second); 58 | } 59 | } 60 | 61 | void test_linked_hash_map_copy() 62 | { 63 | static const number_pair_t numbers[] = { 64 | { 666, 4 }, { 777, 1 }, { 888, 2 }, {999, 3}, {0, 0} 65 | }; 66 | 67 | ethical::linked_hash_map ht = std::move(ethical::linked_hash_map()); 68 | size_t count; 69 | 70 | ht.insert({666, 4}); 71 | ht.insert({777, 1}); 72 | ht.insert({888, 2}); 73 | ht.insert({999, 3}); 74 | 75 | count = 0; 76 | for (auto ent : ht) { 77 | const number_pair_t *n = numbers + count++; 78 | assert(ent.first == n->first); 79 | assert(ent.second == n->second); 80 | } 81 | assert(count == 4); 82 | } 83 | 84 | void test_linked_hash_map_move() 85 | { 86 | static const number_pair_t numbers[] = { 87 | { 666, 4 }, { 777, 1 }, { 888, 2 }, {999, 3}, {0, 0} 88 | }; 89 | 90 | ethical::linked_hash_map hs, ht; 91 | size_t count; 92 | 93 | ht.insert({666, 4}); 94 | ht.insert({777, 1}); 95 | ht.insert({888, 2}); 96 | ht.insert({999, 3}); 97 | 98 | hs = ht; 99 | 100 | count = 0; 101 | for (auto ent : hs) { 102 | const number_pair_t *n = numbers + count++; 103 | assert(ent.first == n->first); 104 | assert(ent.second == n->second); 105 | } 106 | assert(count == 4); 107 | 108 | ethical::linked_hash_map hu(hs); 109 | 110 | count = 0; 111 | for (auto ent : hu) { 112 | const number_pair_t *n = numbers + count++; 113 | assert(ent.first == n->first); 114 | assert(ent.second == n->second); 115 | } 116 | assert(count == 4); 117 | } 118 | 119 | void test_linked_hash_map_delete() 120 | { 121 | ethical::linked_hash_map ht; 122 | ht.insert(7, 8); 123 | assert(ht.find(7)->second == 8); 124 | ht.erase(7); 125 | assert(ht.find(7) == ht.end()); 126 | } 127 | 128 | template 129 | void insert_random(size_t limit, F fn) 130 | { 131 | std::random_device random_device; 132 | std::default_random_engine random_engine; 133 | std::uniform_int_distribution random_dist; 134 | 135 | for (size_t i = 0; i < limit; i++) { 136 | uint64_t key = random_dist(random_engine); 137 | uint64_t val = random_dist(random_engine); 138 | fn(key, val); 139 | } 140 | } 141 | 142 | void test_linked_hash_map_random(size_t limit) 143 | { 144 | ethical::linked_hash_map ht; 145 | std::map hm; 146 | 147 | insert_random(limit, [&](size_t key, size_t val) { 148 | ht.insert(key, val); 149 | hm.insert(hm.end(), std::pair(key, val)); 150 | }); 151 | 152 | for (auto &ent : hm) { 153 | assert(ht.find(ent.first)->second == ent.second); 154 | } 155 | } 156 | 157 | int main(int argc, char **argv) 158 | { 159 | test_linked_hash_map_simple(); 160 | test_linked_hash_map_insert(); 161 | test_linked_hash_map_delete(); 162 | test_linked_hash_map_random(1<<16); 163 | test_linked_hash_map_copy(); 164 | test_linked_hash_map_move(); 165 | return 0; 166 | } -------------------------------------------------------------------------------- /tests/test_linked_hash_set.cc: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "sha256.h" 12 | #include "linked_hash_map.h" 13 | #include "linked_hash_set.h" 14 | 15 | struct hash_linked_hash_map 16 | { 17 | typedef ethical::linked_hash_map hashmap_t; 18 | 19 | size_t operator()(hashmap_t &m) 20 | { 21 | uint8_t b[32]; 22 | sha256_ctx ctx; 23 | sha256_init(&ctx); 24 | for (auto &ent : m) { 25 | sha256_update(&ctx, (const void*)&ent.first, sizeof(ent.first)); 26 | sha256_update(&ctx, (const void*)&ent.second, sizeof(ent.second)); 27 | } 28 | sha256_final(&ctx, b); 29 | return *(size_t*)b; 30 | } 31 | }; 32 | 33 | void test_linked_hash_set_simple() 34 | { 35 | static const uintptr_t numbers[] = { 8, 9, 6, 7, 4, 5, 2, 3, 0 }; 36 | 37 | ethical::linked_hash_set ht; 38 | 39 | for (const uintptr_t *n = numbers; *n != 0; n++) { 40 | ht.insert(*n); 41 | } 42 | for (const uintptr_t *n = numbers; *n != 0; n++) { 43 | assert(ht.find(*n)->first == *n); 44 | } 45 | const uintptr_t *n = numbers; 46 | for (auto &ent : ht) { 47 | assert(ent.first == *n++); 48 | } 49 | } 50 | 51 | ethical::linked_hash_map 52 | make_map(std::initializer_list> l) 53 | { 54 | ethical::linked_hash_map m; 55 | for (auto i = l.begin(); i != l.end(); i++) { 56 | m.insert(i->first, i->second); 57 | } 58 | return std::move(m); 59 | } 60 | 61 | typedef ethical::linked_hash_map hashmap_t; 62 | typedef ethical::linked_hash_set hashset_t; 63 | 64 | void test_linked_hash_set_hashmap() 65 | { 66 | hashset_t s; 67 | s.insert(make_map({{1,2},{3,4},{5,6}})); 68 | s.insert(make_map({{1,2},{3,4},{5,6},{7,8}})); 69 | s.insert(make_map({{1,2},{3,4},{5,6},{7,8},{9,10}})); 70 | 71 | for (auto &h : s) { 72 | for (auto i = h.first.begin(); i != h.first.end(); i++) { 73 | if (i != h.first.begin()) printf(","); 74 | printf("%d=%d", i->first, i->second); 75 | } 76 | printf("\n"); 77 | } 78 | } 79 | 80 | int main(int argc, char **argv) 81 | { 82 | test_linked_hash_set_simple(); 83 | test_linked_hash_set_hashmap(); 84 | return 0; 85 | } -------------------------------------------------------------------------------- /tests/test_sha_delta.cc: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "bytes.h" 13 | #include "sha256.h" 14 | #include "linked_hash_map.h" 15 | 16 | 17 | using ethical::linked_hash_map; 18 | 19 | static char* hex_string(char *buf, size_t buf_len, const uint8_t *in, size_t len) 20 | { 21 | size_t o = 0; 22 | for (size_t i = 0; i < len; i++) { 23 | o += snprintf(buf + o, buf_len - o, "%02" PRIX8, in[i]); 24 | } 25 | return buf; 26 | } 27 | 28 | template static char* map_string(char *buf, size_t buf_len, H &map) 29 | { 30 | size_t o = 0; 31 | for (auto i = map.begin(); i != map.end(); i++) { 32 | if (i != map.begin()) o += snprintf(buf + o, buf_len - o, ", "); 33 | o += snprintf(buf + o, buf_len - o, "%d:%d", i->first, i->second); 34 | } 35 | return buf; 36 | } 37 | 38 | typedef std::array key256; 39 | typedef linked_hash_map pmap; 40 | 41 | struct hash_key256 { 42 | size_t operator()(const key256 &b) { 43 | return le64(*(const uint64_t*)&b[0]); 44 | } 45 | }; 46 | 47 | key256 pmap_key256(pmap &m) 48 | { 49 | key256 b; 50 | sha256_ctx ctx; 51 | sha256_init(&ctx); 52 | for (auto &ent : m) { 53 | sha256_update(&ctx, (const void*)&ent.first, sizeof(ent.first)); 54 | sha256_update(&ctx, (const void*)&ent.second, sizeof(ent.second)); 55 | } 56 | sha256_final(&ctx, b.data()); 57 | return b; 58 | } 59 | 60 | pmap make_map(std::initializer_list> l) 61 | { 62 | pmap m; 63 | std::for_each(l.begin(), l.end(), [&](const auto &i) { m.insert(i); }); 64 | return std::move(m); 65 | } 66 | 67 | typedef linked_hash_map sha256_pmap_base; 68 | 69 | struct sha256_pmap : sha256_pmap_base 70 | { 71 | void insert(pmap &&m) { sha256_pmap_base::insert(pmap_key256(m), m); } 72 | }; 73 | 74 | void test_hashmap_hashmap() 75 | { 76 | sha256_pmap s; 77 | s.insert(make_map({{0,1},{1,4},{2,120},{3,60}})); 78 | s.insert(make_map({{0,2},{1,4},{2,240},{3,180}})); 79 | s.insert(make_map({{0,3},{1,4},{2,720},{3,360}})); 80 | s.insert(make_map({{0,4},{1,4},{2,1260},{3,840}})); 81 | s.insert(make_map({{0,5},{1,8},{2,2520},{3,1680}})); 82 | s.insert(make_map({{0,6},{1,8},{2,7560},{3,5040}})); 83 | s.insert(make_map({{0,7},{1,8},{2,15120},{3,10080}})); 84 | s.insert(make_map({{0,8},{1,8},{2,25200},{3,20160}})); 85 | s.insert(make_map({{0,9},{1,12},{2,45360},{3,27720}})); 86 | s.insert(make_map({{0,10},{1,12},{2,55440},{3,50400}})); 87 | s.insert(make_map({{0,11},{1,12},{2,110880},{3,83160}})); 88 | s.insert(make_map({{0,12},{1,12},{2,221760},{3,166320}})); 89 | s.insert(make_map({{0,13},{1,16},{2,332640},{3,277200}})); 90 | s.insert(make_map({{0,14},{1,16},{2,554400},{3,498960}})); 91 | s.insert(make_map({{0,15},{1,16},{2,720720},{3,665280}})); 92 | s.insert(make_map({{0,16},{1,16},{2,1441440},{3,1081080}})); 93 | 94 | char hs[128], ms[128]; 95 | for (auto &h : s) { 96 | printf("%s → { %s }\n", hex_string(hs, sizeof(hs), &h.first[0], 16), 97 | map_string(ms, sizeof(ms), h.second)); 98 | } 99 | } 100 | 101 | /* 102 | starting an experiment with graph deltas to efficiently modify graph 103 | replicas in separate processes. like Git but finer-grained. 104 | a doubly-linked map of maps indexed by truncated SHA2 hash. 105 | a truncated content hash of the object used to address nodes. 106 | */ 107 | 108 | int main(int argc, char **argv) 109 | { 110 | test_hashmap_hashmap(); 111 | return 0; 112 | } --------------------------------------------------------------------------------