authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-03 17:11:54+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-07-03 17:11:54+00:00
log70dca0a0c6fd27bc39ac3a37edd2a6908bc0198f
treef33343e213b7e29e113a12dcd39131b2bc612ceb
parentf281b928d995ff68f4115fb5a9b7aa10f8c60322
parent22f0a103c39f84140ee1fbfe2bffed5fcec19a26
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5779 from ziglang/stage1-hash-map

stage1 HashMap: store hash & do robin hood hashing

2 files changed, 148 insertions(+), 36 deletions(-)

src/hash_map.hpp+145-36
...@@ -25,6 +25,8 @@ public:...@@ -25,6 +25,8 @@ public:
25 }25 }
2626
27 struct Entry {27 struct Entry {
28 uint32_t hash;
29 uint32_t distance_from_start_index;
28 K key;30 K key;
29 V value;31 V value;
30 };32 };
...@@ -43,6 +45,26 @@ public:...@@ -43,6 +45,26 @@ public:
43 void put(const K &key, const V &value) {45 void put(const K &key, const V &value) {
44 _modification_count += 1;46 _modification_count += 1;
4547
48 // This allows us to take a pointer to an entry in `internal_put` which
49 // will not become a dead pointer when the array list is appended.
50 _entries.ensure_capacity(_entries.length + 1);
51
52 if (_index_bytes == nullptr) {
53 if (_entries.length < 16) {
54 _entries.append({HashFunction(key), 0, key, value});
55 return;
56 } else {
57 _indexes_len = 32;
58 _index_bytes = heap::c_allocator.allocate<uint8_t>(_indexes_len);
59 _max_distance_from_start_index = 0;
60 for (size_t i = 0; i < _entries.length; i += 1) {
61 Entry *entry = &_entries.items[i];
62 put_index(entry, i, _index_bytes);
63 }
64 return internal_put(key, value, _index_bytes);
65 }
66 }
67
46 // if we would get too full (60%), double the indexes size68 // if we would get too full (60%), double the indexes size
47 if ((_entries.length + 1) * 5 >= _indexes_len * 3) {69 if ((_entries.length + 1) * 5 >= _indexes_len * 3) {
48 heap::c_allocator.deallocate(_index_bytes,70 heap::c_allocator.deallocate(_index_bytes,
...@@ -56,22 +78,21 @@ public:...@@ -56,22 +78,21 @@ public:
56 Entry *entry = &_entries.items[i];78 Entry *entry = &_entries.items[i];
57 switch (sz) {79 switch (sz) {
58 case 1:80 case 1:
59 put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes);81 put_index(entry, i, (uint8_t*)_index_bytes);
60 continue;82 continue;
61 case 2:83 case 2:
62 put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes);84 put_index(entry, i, (uint16_t*)_index_bytes);
63 continue;85 continue;
64 case 4:86 case 4:
65 put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes);87 put_index(entry, i, (uint32_t*)_index_bytes);
66 continue;88 continue;
67 default:89 default:
68 put_index(key_to_index(entry->key), i, (size_t*)_index_bytes);90 put_index(entry, i, (size_t*)_index_bytes);
69 continue;91 continue;
70 }92 }
71 }93 }
72 }94 }
7395
74
75 switch (capacity_index_size(_indexes_len)) {96 switch (capacity_index_size(_indexes_len)) {
76 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);97 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);
77 case 2: return internal_put(key, value, (uint16_t*)_index_bytes);98 case 2: return internal_put(key, value, (uint16_t*)_index_bytes);
...@@ -109,6 +130,16 @@ public:...@@ -109,6 +130,16 @@ public:
109130
110 bool maybe_remove(const K &key) {131 bool maybe_remove(const K &key) {
111 _modification_count += 1;132 _modification_count += 1;
133 if (_index_bytes == nullptr) {
134 uint32_t hash = HashFunction(key);
135 for (size_t i = 0; i < _entries.length; i += 1) {
136 if (_entries.items[i].hash == hash && EqualFn(_entries.items[i].key, key)) {
137 _entries.swap_remove(i);
138 return true;
139 }
140 }
141 return false;
142 }
112 switch (capacity_index_size(_indexes_len)) {143 switch (capacity_index_size(_indexes_len)) {
113 case 1: return internal_remove(key, (uint8_t*)_index_bytes);144 case 1: return internal_remove(key, (uint8_t*)_index_bytes);
114 case 2: return internal_remove(key, (uint16_t*)_index_bytes);145 case 2: return internal_remove(key, (uint16_t*)_index_bytes);
...@@ -165,11 +196,16 @@ private:...@@ -165,11 +196,16 @@ private:
165 void init_capacity(size_t capacity) {196 void init_capacity(size_t capacity) {
166 _entries = {};197 _entries = {};
167 _entries.ensure_capacity(capacity);198 _entries.ensure_capacity(capacity);
168 // So that at capacity it will only be 60% full.199 _indexes_len = 0;
169 _indexes_len = capacity * 5 / 3;200 if (capacity >= 16) {
170 size_t sz = capacity_index_size(_indexes_len);201 // So that at capacity it will only be 60% full.
171 // This zero initializes _index_bytes which sets them all to empty.202 _indexes_len = capacity * 5 / 3;
172 _index_bytes = heap::c_allocator.allocate<uint8_t>(_indexes_len * sz);203 size_t sz = capacity_index_size(_indexes_len);
204 // This zero initializes _index_bytes which sets them all to empty.
205 _index_bytes = heap::c_allocator.allocate<uint8_t>(_indexes_len * sz);
206 } else {
207 _index_bytes = nullptr;
208 }
173209
174 _max_distance_from_start_index = 0;210 _max_distance_from_start_index = 0;
175 _modification_count = 0;211 _modification_count = 0;
...@@ -187,47 +223,113 @@ private:...@@ -187,47 +223,113 @@ private:
187223
188 template <typename I>224 template <typename I>
189 void internal_put(const K &key, const V &value, I *indexes) {225 void internal_put(const K &key, const V &value, I *indexes) {
190 size_t start_index = key_to_index(key);226 uint32_t hash = HashFunction(key);
191 for (size_t roll_over = 0, distance_from_start_index = 0;227 uint32_t distance_from_start_index = 0;
192 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)228 size_t start_index = hash_to_index(hash);
229 for (size_t roll_over = 0; roll_over < _indexes_len;
230 roll_over += 1, distance_from_start_index += 1)
193 {231 {
194 size_t index_index = (start_index + roll_over) % _indexes_len;232 size_t index_index = (start_index + roll_over) % _indexes_len;
195 I index_data = indexes[index_index];233 I index_data = indexes[index_index];
196 if (index_data == 0) {234 if (index_data == 0) {
197 _entries.append({key, value});235 _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value });
198 indexes[index_index] = _entries.length;236 indexes[index_index] = _entries.length;
199 if (distance_from_start_index > _max_distance_from_start_index)237 if (distance_from_start_index > _max_distance_from_start_index)
200 _max_distance_from_start_index = distance_from_start_index;238 _max_distance_from_start_index = distance_from_start_index;
201 return;239 return;
202 }240 }
241 // This pointer survives the following append because we call
242 // _entries.ensure_capacity before internal_put.
203 Entry *entry = &_entries.items[index_data - 1];243 Entry *entry = &_entries.items[index_data - 1];
204 if (EqualFn(entry->key, key)) {244 if (entry->hash == hash && EqualFn(entry->key, key)) {
205 *entry = {key, value};245 *entry = {hash, distance_from_start_index, key, value};
206 if (distance_from_start_index > _max_distance_from_start_index)246 if (distance_from_start_index > _max_distance_from_start_index)
207 _max_distance_from_start_index = distance_from_start_index;247 _max_distance_from_start_index = distance_from_start_index;
208 return;248 return;
209 }249 }
250 if (entry->distance_from_start_index < distance_from_start_index) {
251 // In this case, we did not find the item. We will put a new entry.
252 // However, we will use this index for the new entry, and move
253 // the previous index down the line, to keep the _max_distance_from_start_index
254 // as small as possible.
255 _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value });
256 indexes[index_index] = _entries.length;
257 if (distance_from_start_index > _max_distance_from_start_index)
258 _max_distance_from_start_index = distance_from_start_index;
259
260 distance_from_start_index = entry->distance_from_start_index;
261
262 // Find somewhere to put the index we replaced by shifting
263 // following indexes backwards.
264 roll_over += 1;
265 distance_from_start_index += 1;
266 for (; roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) {
267 size_t index_index = (start_index + roll_over) % _indexes_len;
268 I next_index_data = indexes[index_index];
269 if (next_index_data == 0) {
270 if (distance_from_start_index > _max_distance_from_start_index)
271 _max_distance_from_start_index = distance_from_start_index;
272 entry->distance_from_start_index = distance_from_start_index;
273 indexes[index_index] = index_data;
274 return;
275 }
276 Entry *next_entry = &_entries.items[next_index_data - 1];
277 if (next_entry->distance_from_start_index < distance_from_start_index) {
278 if (distance_from_start_index > _max_distance_from_start_index)
279 _max_distance_from_start_index = distance_from_start_index;
280 entry->distance_from_start_index = distance_from_start_index;
281 indexes[index_index] = index_data;
282 distance_from_start_index = next_entry->distance_from_start_index;
283 entry = next_entry;
284 index_data = next_index_data;
285 }
286 }
287 zig_unreachable();
288 }
210 }289 }
211 zig_unreachable();290 zig_unreachable();
212 }291 }
213292
214 template <typename I>293 template <typename I>
215 void put_index(size_t start_index, size_t entry_index, I *indexes) {294 void put_index(Entry *entry, size_t entry_index, I *indexes) {
295 size_t start_index = hash_to_index(entry->hash);
296 size_t index_data = entry_index + 1;
216 for (size_t roll_over = 0, distance_from_start_index = 0;297 for (size_t roll_over = 0, distance_from_start_index = 0;
217 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)298 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)
218 {299 {
219 size_t index_index = (start_index + roll_over) % _indexes_len;300 size_t index_index = (start_index + roll_over) % _indexes_len;
220 if (indexes[index_index] == 0) {301 size_t next_index_data = indexes[index_index];
221 indexes[index_index] = entry_index + 1;302 if (next_index_data == 0) {
222 if (distance_from_start_index > _max_distance_from_start_index)303 if (distance_from_start_index > _max_distance_from_start_index)
223 _max_distance_from_start_index = distance_from_start_index;304 _max_distance_from_start_index = distance_from_start_index;
305 entry->distance_from_start_index = distance_from_start_index;
306 indexes[index_index] = index_data;
224 return;307 return;
225 }308 }
309 Entry *next_entry = &_entries.items[next_index_data - 1];
310 if (next_entry->distance_from_start_index < distance_from_start_index) {
311 if (distance_from_start_index > _max_distance_from_start_index)
312 _max_distance_from_start_index = distance_from_start_index;
313 entry->distance_from_start_index = distance_from_start_index;
314 indexes[index_index] = index_data;
315 distance_from_start_index = next_entry->distance_from_start_index;
316 entry = next_entry;
317 index_data = next_index_data;
318 }
226 }319 }
227 zig_unreachable();320 zig_unreachable();
228 }321 }
229322
230 Entry *internal_get(const K &key) const {323 Entry *internal_get(const K &key) const {
324 if (_index_bytes == nullptr) {
325 uint32_t hash = HashFunction(key);
326 for (size_t i = 0; i < _entries.length; i += 1) {
327 if (_entries.items[i].hash == hash && EqualFn(_entries.items[i].key, key)) {
328 return &_entries.items[i];
329 }
330 }
331 return nullptr;
332 }
231 switch (capacity_index_size(_indexes_len)) {333 switch (capacity_index_size(_indexes_len)) {
232 case 1: return internal_get2(key, (uint8_t*)_index_bytes);334 case 1: return internal_get2(key, (uint8_t*)_index_bytes);
233 case 2: return internal_get2(key, (uint16_t*)_index_bytes);335 case 2: return internal_get2(key, (uint16_t*)_index_bytes);
...@@ -238,7 +340,8 @@ private:...@@ -238,7 +340,8 @@ private:
238340
239 template <typename I>341 template <typename I>
240 Entry *internal_get2(const K &key, I *indexes) const {342 Entry *internal_get2(const K &key, I *indexes) const {
241 size_t start_index = key_to_index(key);343 uint32_t hash = HashFunction(key);
344 size_t start_index = hash_to_index(hash);
242 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {345 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
243 size_t index_index = (start_index + roll_over) % _indexes_len;346 size_t index_index = (start_index + roll_over) % _indexes_len;
244 size_t index_data = indexes[index_index];347 size_t index_data = indexes[index_index];
...@@ -246,19 +349,20 @@ private:...@@ -246,19 +349,20 @@ private:
246 return nullptr;349 return nullptr;
247350
248 Entry *entry = &_entries.items[index_data - 1];351 Entry *entry = &_entries.items[index_data - 1];
249 if (EqualFn(entry->key, key))352 if (entry->hash == hash && EqualFn(entry->key, key))
250 return entry;353 return entry;
251 }354 }
252 return nullptr;355 return nullptr;
253 }356 }
254357
255 size_t key_to_index(const K &key) const {358 size_t hash_to_index(uint32_t hash) const {
256 return ((size_t)HashFunction(key)) % _indexes_len;359 return ((size_t)hash) % _indexes_len;
257 }360 }
258361
259 template <typename I>362 template <typename I>
260 bool internal_remove(const K &key, I *indexes) {363 bool internal_remove(const K &key, I *indexes) {
261 size_t start_index = key_to_index(key);364 uint32_t hash = HashFunction(key);
365 size_t start_index = hash_to_index(hash);
262 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {366 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
263 size_t index_index = (start_index + roll_over) % _indexes_len;367 size_t index_index = (start_index + roll_over) % _indexes_len;
264 size_t index_data = indexes[index_index];368 size_t index_data = indexes[index_index];
...@@ -267,10 +371,10 @@ private:...@@ -267,10 +371,10 @@ private:
267371
268 size_t index = index_data - 1;372 size_t index = index_data - 1;
269 Entry *entry = &_entries.items[index];373 Entry *entry = &_entries.items[index];
270 if (!EqualFn(entry->key, key))374 if (entry->hash != hash || !EqualFn(entry->key, key))
271 continue;375 continue;
272376
273 indexes[index_index] = 0;377 size_t prev_index = index_index;
274 _entries.swap_remove(index);378 _entries.swap_remove(index);
275 if (_entries.length > 0 && _entries.length != index) {379 if (_entries.length > 0 && _entries.length != index) {
276 // Because of the swap remove, now we need to update the index that was380 // Because of the swap remove, now we need to update the index that was
...@@ -280,24 +384,29 @@ private:...@@ -280,24 +384,29 @@ private:
280384
281 // Now we have to shift over the following indexes.385 // Now we have to shift over the following indexes.
282 roll_over += 1;386 roll_over += 1;
283 for (; roll_over <= _max_distance_from_start_index; roll_over += 1) {387 for (; roll_over < _indexes_len; roll_over += 1) {
284 size_t next_index = (start_index + roll_over) % _indexes_len;388 size_t next_index = (start_index + roll_over) % _indexes_len;
285 if (indexes[next_index] == 0)389 if (indexes[next_index] == 0) {
286 break;390 indexes[prev_index] = 0;
287 size_t next_start_index = key_to_index(_entries.items[indexes[next_index]].key);391 return true;
288 if (next_start_index != start_index)392 }
289 break;393 Entry *next_entry = &_entries.items[indexes[next_index] - 1];
290 indexes[next_index - 1] = indexes[next_index];394 if (next_entry->distance_from_start_index == 0) {
395 indexes[prev_index] = 0;
396 return true;
397 }
398 indexes[prev_index] = indexes[next_index];
399 prev_index = next_index;
400 next_entry->distance_from_start_index -= 1;
291 }401 }
292402 zig_unreachable();
293 return true;
294 }403 }
295 return false;404 return false;
296 }405 }
297406
298 template <typename I>407 template <typename I>
299 void update_entry_index(size_t old_entry_index, size_t new_entry_index, I *indexes) {408 void update_entry_index(size_t old_entry_index, size_t new_entry_index, I *indexes) {
300 size_t start_index = key_to_index(_entries.items[new_entry_index].key);409 size_t start_index = hash_to_index(_entries.items[new_entry_index].hash);
301 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {410 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
302 size_t index_index = (start_index + roll_over) % _indexes_len;411 size_t index_index = (start_index + roll_over) % _indexes_len;
303 if (indexes[index_index] == old_entry_index + 1) {412 if (indexes[index_index] == old_entry_index + 1) {
src/list.hpp+3
...@@ -19,6 +19,9 @@ struct ZigList {...@@ -19,6 +19,9 @@ struct ZigList {
19 ensure_capacity(length + 1);19 ensure_capacity(length + 1);
20 items[length++] = item;20 items[length++] = item;
21 }21 }
22 void append_assuming_capacity(const T& item) {
23 items[length++] = item;
24 }
22 // remember that the pointer to this item is invalid after you25 // remember that the pointer to this item is invalid after you
23 // modify the length of the list26 // modify the length of the list
24 const T & at(size_t index) const {27 const T & at(size_t index) const {