authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-02 22:33:52+00:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-02 22:38:55+00:00
logdf2c27eb486383a291dfe1db3dfda51f830f59c5
tree9af0034e86f206850d3ec5d1fa66582543c490b5
parent70cc1751ca69dd77625c703445713d067215b5d9

stage1 HashMap: store hash & do robin hood hashing

This adds these two fields to a HashMap Entry: uint32_t hash uint32_t distance_from_start_index Compared to master branch, standard library tests compiled 8.4% faster and took negligible (0.001%) more memory to complete. The amount of memory used is still down from before 8b82c4010480 which moved indexes to be stored separately from entries. So, it turns out, keeping robin hood hashing plus separating indexes did result in a performance improvement. What happened previously is that the gains from separating indexes balanced out the losses from removing robin hood hashing, resulting in a wash. This also serves as an inspiration for adding a benchmark to std.AutoHashMap and improving the implementation.

2 files changed, 102 insertions(+), 30 deletions(-)

src/hash_map.hpp+99-30
...@@ -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 };
...@@ -56,21 +58,24 @@ public:...@@ -56,21 +58,24 @@ public:
56 Entry *entry = &_entries.items[i];58 Entry *entry = &_entries.items[i];
57 switch (sz) {59 switch (sz) {
58 case 1:60 case 1:
59 put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes);61 put_index(entry, i, (uint8_t*)_index_bytes);
60 continue;62 continue;
61 case 2:63 case 2:
62 put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes);64 put_index(entry, i, (uint16_t*)_index_bytes);
63 continue;65 continue;
64 case 4:66 case 4:
65 put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes);67 put_index(entry, i, (uint32_t*)_index_bytes);
66 continue;68 continue;
67 default:69 default:
68 put_index(key_to_index(entry->key), i, (size_t*)_index_bytes);70 put_index(entry, i, (size_t*)_index_bytes);
69 continue;71 continue;
70 }72 }
71 }73 }
72 }74 }
7375
76 // This allows us to take a pointer to an entry in `internal_put` which
77 // will not become a dead pointer when the array list is appended.
78 _entries.ensure_capacity(_entries.length + 1);
7479
75 switch (capacity_index_size(_indexes_len)) {80 switch (capacity_index_size(_indexes_len)) {
76 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);81 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);
...@@ -187,42 +192,99 @@ private:...@@ -187,42 +192,99 @@ private:
187192
188 template <typename I>193 template <typename I>
189 void internal_put(const K &key, const V &value, I *indexes) {194 void internal_put(const K &key, const V &value, I *indexes) {
190 size_t start_index = key_to_index(key);195 uint32_t hash = HashFunction(key);
191 for (size_t roll_over = 0, distance_from_start_index = 0;196 uint32_t distance_from_start_index = 0;
192 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)197 size_t start_index = hash_to_index(hash);
198 for (size_t roll_over = 0; roll_over < _indexes_len;
199 roll_over += 1, distance_from_start_index += 1)
193 {200 {
194 size_t index_index = (start_index + roll_over) % _indexes_len;201 size_t index_index = (start_index + roll_over) % _indexes_len;
195 I index_data = indexes[index_index];202 I index_data = indexes[index_index];
196 if (index_data == 0) {203 if (index_data == 0) {
197 _entries.append({key, value});204 _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value });
198 indexes[index_index] = _entries.length;205 indexes[index_index] = _entries.length;
199 if (distance_from_start_index > _max_distance_from_start_index)206 if (distance_from_start_index > _max_distance_from_start_index)
200 _max_distance_from_start_index = distance_from_start_index;207 _max_distance_from_start_index = distance_from_start_index;
201 return;208 return;
202 }209 }
210 // This pointer survives the following append because we call
211 // _entries.ensure_capacity before internal_put.
203 Entry *entry = &_entries.items[index_data - 1];212 Entry *entry = &_entries.items[index_data - 1];
204 if (EqualFn(entry->key, key)) {213 if (entry->hash == hash && EqualFn(entry->key, key)) {
205 *entry = {key, value};214 *entry = {hash, distance_from_start_index, key, value};
206 if (distance_from_start_index > _max_distance_from_start_index)215 if (distance_from_start_index > _max_distance_from_start_index)
207 _max_distance_from_start_index = distance_from_start_index;216 _max_distance_from_start_index = distance_from_start_index;
208 return;217 return;
209 }218 }
219 if (entry->distance_from_start_index < distance_from_start_index) {
220 // In this case, we did not find the item. We will put a new entry.
221 // However, we will use this index for the new entry, and move
222 // the previous index down the line, to keep the _max_distance_from_start_index
223 // as small as possible.
224 _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value });
225 indexes[index_index] = _entries.length;
226 if (distance_from_start_index > _max_distance_from_start_index)
227 _max_distance_from_start_index = distance_from_start_index;
228
229 distance_from_start_index = entry->distance_from_start_index;
230
231 // Find somewhere to put the index we replaced by shifting
232 // following indexes backwards.
233 roll_over += 1;
234 distance_from_start_index += 1;
235 for (; roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) {
236 size_t index_index = (start_index + roll_over) % _indexes_len;
237 I next_index_data = indexes[index_index];
238 if (next_index_data == 0) {
239 if (distance_from_start_index > _max_distance_from_start_index)
240 _max_distance_from_start_index = distance_from_start_index;
241 entry->distance_from_start_index = distance_from_start_index;
242 indexes[index_index] = index_data;
243 return;
244 }
245 Entry *next_entry = &_entries.items[next_index_data - 1];
246 if (next_entry->distance_from_start_index < distance_from_start_index) {
247 if (distance_from_start_index > _max_distance_from_start_index)
248 _max_distance_from_start_index = distance_from_start_index;
249 entry->distance_from_start_index = distance_from_start_index;
250 indexes[index_index] = index_data;
251 distance_from_start_index = next_entry->distance_from_start_index;
252 entry = next_entry;
253 index_data = next_index_data;
254 }
255 }
256 zig_unreachable();
257 }
210 }258 }
211 zig_unreachable();259 zig_unreachable();
212 }260 }
213261
214 template <typename I>262 template <typename I>
215 void put_index(size_t start_index, size_t entry_index, I *indexes) {263 void put_index(Entry *entry, size_t entry_index, I *indexes) {
264 size_t start_index = hash_to_index(entry->hash);
265 size_t index_data = entry_index + 1;
216 for (size_t roll_over = 0, distance_from_start_index = 0;266 for (size_t roll_over = 0, distance_from_start_index = 0;
217 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)267 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)
218 {268 {
219 size_t index_index = (start_index + roll_over) % _indexes_len;269 size_t index_index = (start_index + roll_over) % _indexes_len;
220 if (indexes[index_index] == 0) {270 size_t next_index_data = indexes[index_index];
221 indexes[index_index] = entry_index + 1;271 if (next_index_data == 0) {
222 if (distance_from_start_index > _max_distance_from_start_index)272 if (distance_from_start_index > _max_distance_from_start_index)
223 _max_distance_from_start_index = distance_from_start_index;273 _max_distance_from_start_index = distance_from_start_index;
274 entry->distance_from_start_index = distance_from_start_index;
275 indexes[index_index] = index_data;
224 return;276 return;
225 }277 }
278 Entry *next_entry = &_entries.items[next_index_data - 1];
279 if (next_entry->distance_from_start_index < distance_from_start_index) {
280 if (distance_from_start_index > _max_distance_from_start_index)
281 _max_distance_from_start_index = distance_from_start_index;
282 entry->distance_from_start_index = distance_from_start_index;
283 indexes[index_index] = index_data;
284 distance_from_start_index = next_entry->distance_from_start_index;
285 entry = next_entry;
286 index_data = next_index_data;
287 }
226 }288 }
227 zig_unreachable();289 zig_unreachable();
228 }290 }
...@@ -238,7 +300,8 @@ private:...@@ -238,7 +300,8 @@ private:
238300
239 template <typename I>301 template <typename I>
240 Entry *internal_get2(const K &key, I *indexes) const {302 Entry *internal_get2(const K &key, I *indexes) const {
241 size_t start_index = key_to_index(key);303 uint32_t hash = HashFunction(key);
304 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) {305 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;306 size_t index_index = (start_index + roll_over) % _indexes_len;
244 size_t index_data = indexes[index_index];307 size_t index_data = indexes[index_index];
...@@ -246,19 +309,20 @@ private:...@@ -246,19 +309,20 @@ private:
246 return nullptr;309 return nullptr;
247310
248 Entry *entry = &_entries.items[index_data - 1];311 Entry *entry = &_entries.items[index_data - 1];
249 if (EqualFn(entry->key, key))312 if (entry->hash == hash && EqualFn(entry->key, key))
250 return entry;313 return entry;
251 }314 }
252 return nullptr;315 return nullptr;
253 }316 }
254317
255 size_t key_to_index(const K &key) const {318 size_t hash_to_index(uint32_t hash) const {
256 return ((size_t)HashFunction(key)) % _indexes_len;319 return ((size_t)hash) % _indexes_len;
257 }320 }
258321
259 template <typename I>322 template <typename I>
260 bool internal_remove(const K &key, I *indexes) {323 bool internal_remove(const K &key, I *indexes) {
261 size_t start_index = key_to_index(key);324 uint32_t hash = HashFunction(key);
325 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) {326 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;327 size_t index_index = (start_index + roll_over) % _indexes_len;
264 size_t index_data = indexes[index_index];328 size_t index_data = indexes[index_index];
...@@ -267,10 +331,10 @@ private:...@@ -267,10 +331,10 @@ private:
267331
268 size_t index = index_data - 1;332 size_t index = index_data - 1;
269 Entry *entry = &_entries.items[index];333 Entry *entry = &_entries.items[index];
270 if (!EqualFn(entry->key, key))334 if (entry->hash != hash || !EqualFn(entry->key, key))
271 continue;335 continue;
272336
273 indexes[index_index] = 0;337 size_t prev_index = index_index;
274 _entries.swap_remove(index);338 _entries.swap_remove(index);
275 if (_entries.length > 0 && _entries.length != index) {339 if (_entries.length > 0 && _entries.length != index) {
276 // Because of the swap remove, now we need to update the index that was340 // Because of the swap remove, now we need to update the index that was
...@@ -280,24 +344,29 @@ private:...@@ -280,24 +344,29 @@ private:
280344
281 // Now we have to shift over the following indexes.345 // Now we have to shift over the following indexes.
282 roll_over += 1;346 roll_over += 1;
283 for (; roll_over <= _max_distance_from_start_index; roll_over += 1) {347 for (; roll_over < _indexes_len; roll_over += 1) {
284 size_t next_index = (start_index + roll_over) % _indexes_len;348 size_t next_index = (start_index + roll_over) % _indexes_len;
285 if (indexes[next_index] == 0)349 if (indexes[next_index] == 0) {
286 break;350 indexes[prev_index] = 0;
287 size_t next_start_index = key_to_index(_entries.items[indexes[next_index]].key);351 return true;
288 if (next_start_index != start_index)352 }
289 break;353 Entry *next_entry = &_entries.items[indexes[next_index] - 1];
290 indexes[next_index - 1] = indexes[next_index];354 if (next_entry->distance_from_start_index == 0) {
355 indexes[prev_index] = 0;
356 return true;
357 }
358 indexes[prev_index] = indexes[next_index];
359 prev_index = next_index;
360 next_entry->distance_from_start_index -= 1;
291 }361 }
292362 zig_unreachable();
293 return true;
294 }363 }
295 return false;364 return false;
296 }365 }
297366
298 template <typename I>367 template <typename I>
299 void update_entry_index(size_t old_entry_index, size_t new_entry_index, I *indexes) {368 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);369 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) {370 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;371 size_t index_index = (start_index + roll_over) % _indexes_len;
303 if (indexes[index_index] == old_entry_index + 1) {372 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 {