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:
2525 }
2626
2727 struct Entry {
28 uint32_t hash;
29 uint32_t distance_from_start_index;
2830 K key;
2931 V value;
3032 };
......@@ -56,21 +58,24 @@ public:
5658 Entry *entry = &_entries.items[i];
5759 switch (sz) {
5860 case 1:
59 put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes);
61 put_index(entry, i, (uint8_t*)_index_bytes);
6062 continue;
6163 case 2:
62 put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes);
64 put_index(entry, i, (uint16_t*)_index_bytes);
6365 continue;
6466 case 4:
65 put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes);
67 put_index(entry, i, (uint32_t*)_index_bytes);
6668 continue;
6769 default:
68 put_index(key_to_index(entry->key), i, (size_t*)_index_bytes);
70 put_index(entry, i, (size_t*)_index_bytes);
6971 continue;
7072 }
7173 }
7274 }
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
7580 switch (capacity_index_size(_indexes_len)) {
7681 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);
......@@ -187,42 +192,99 @@ private:
187192
188193 template <typename I>
189194 void internal_put(const K &key, const V &value, I *indexes) {
190 size_t start_index = key_to_index(key);
191 for (size_t roll_over = 0, distance_from_start_index = 0;
192 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)
195 uint32_t hash = HashFunction(key);
196 uint32_t distance_from_start_index = 0;
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)
193200 {
194201 size_t index_index = (start_index + roll_over) % _indexes_len;
195202 I index_data = indexes[index_index];
196203 if (index_data == 0) {
197 _entries.append({key, value});
204 _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value });
198205 indexes[index_index] = _entries.length;
199206 if (distance_from_start_index > _max_distance_from_start_index)
200207 _max_distance_from_start_index = distance_from_start_index;
201208 return;
202209 }
210 // This pointer survives the following append because we call
211 // _entries.ensure_capacity before internal_put.
203212 Entry *entry = &_entries.items[index_data - 1];
204 if (EqualFn(entry->key, key)) {
205 *entry = {key, value};
213 if (entry->hash == hash && EqualFn(entry->key, key)) {
214 *entry = {hash, distance_from_start_index, key, value};
206215 if (distance_from_start_index > _max_distance_from_start_index)
207216 _max_distance_from_start_index = distance_from_start_index;
208217 return;
209218 }
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 }
210258 }
211259 zig_unreachable();
212260 }
213261
214262 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;
216266 for (size_t roll_over = 0, distance_from_start_index = 0;
217267 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)
218268 {
219269 size_t index_index = (start_index + roll_over) % _indexes_len;
220 if (indexes[index_index] == 0) {
221 indexes[index_index] = entry_index + 1;
270 size_t next_index_data = indexes[index_index];
271 if (next_index_data == 0) {
222272 if (distance_from_start_index > _max_distance_from_start_index)
223273 _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;
224276 return;
225277 }
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 }
226288 }
227289 zig_unreachable();
228290 }
......@@ -238,7 +300,8 @@ private:
238300
239301 template <typename I>
240302 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);
242305 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
243306 size_t index_index = (start_index + roll_over) % _indexes_len;
244307 size_t index_data = indexes[index_index];
......@@ -246,19 +309,20 @@ private:
246309 return nullptr;
247310
248311 Entry *entry = &_entries.items[index_data - 1];
249 if (EqualFn(entry->key, key))
312 if (entry->hash == hash && EqualFn(entry->key, key))
250313 return entry;
251314 }
252315 return nullptr;
253316 }
254317
255 size_t key_to_index(const K &key) const {
256 return ((size_t)HashFunction(key)) % _indexes_len;
318 size_t hash_to_index(uint32_t hash) const {
319 return ((size_t)hash) % _indexes_len;
257320 }
258321
259322 template <typename I>
260323 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);
262326 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
263327 size_t index_index = (start_index + roll_over) % _indexes_len;
264328 size_t index_data = indexes[index_index];
......@@ -267,10 +331,10 @@ private:
267331
268332 size_t index = index_data - 1;
269333 Entry *entry = &_entries.items[index];
270 if (!EqualFn(entry->key, key))
334 if (entry->hash != hash || !EqualFn(entry->key, key))
271335 continue;
272336
273 indexes[index_index] = 0;
337 size_t prev_index = index_index;
274338 _entries.swap_remove(index);
275339 if (_entries.length > 0 && _entries.length != index) {
276340 // Because of the swap remove, now we need to update the index that was
......@@ -280,24 +344,29 @@ private:
280344
281345 // Now we have to shift over the following indexes.
282346 roll_over += 1;
283 for (; roll_over <= _max_distance_from_start_index; roll_over += 1) {
347 for (; roll_over < _indexes_len; roll_over += 1) {
284348 size_t next_index = (start_index + roll_over) % _indexes_len;
285 if (indexes[next_index] == 0)
286 break;
287 size_t next_start_index = key_to_index(_entries.items[indexes[next_index]].key);
288 if (next_start_index != start_index)
289 break;
290 indexes[next_index - 1] = indexes[next_index];
349 if (indexes[next_index] == 0) {
350 indexes[prev_index] = 0;
351 return true;
352 }
353 Entry *next_entry = &_entries.items[indexes[next_index] - 1];
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;
291361 }
292
293 return true;
362 zig_unreachable();
294363 }
295364 return false;
296365 }
297366
298367 template <typename I>
299368 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);
301370 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
302371 size_t index_index = (start_index + roll_over) % _indexes_len;
303372 if (indexes[index_index] == old_entry_index + 1) {
src/list.hpp+3
......@@ -19,6 +19,9 @@ struct ZigList {
1919 ensure_capacity(length + 1);
2020 items[length++] = item;
2121 }
22 void append_assuming_capacity(const T& item) {
23 items[length++] = item;
24 }
2225 // remember that the pointer to this item is invalid after you
2326 // modify the length of the list
2427 const T & at(size_t index) const {