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
signature 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:
2525 }
2626
2727 struct Entry {
28 uint32_t hash;
29 uint32_t distance_from_start_index;
2830 K key;
2931 V value;
3032 };
......@@ -43,6 +45,26 @@ public:
4345 void put(const K &key, const V &value) {
4446 _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
4668 // if we would get too full (60%), double the indexes size
4769 if ((_entries.length + 1) * 5 >= _indexes_len * 3) {
4870 heap::c_allocator.deallocate(_index_bytes,
......@@ -56,22 +78,21 @@ public:
5678 Entry *entry = &_entries.items[i];
5779 switch (sz) {
5880 case 1:
59 put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes);
81 put_index(entry, i, (uint8_t*)_index_bytes);
6082 continue;
6183 case 2:
62 put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes);
84 put_index(entry, i, (uint16_t*)_index_bytes);
6385 continue;
6486 case 4:
65 put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes);
87 put_index(entry, i, (uint32_t*)_index_bytes);
6688 continue;
6789 default:
68 put_index(key_to_index(entry->key), i, (size_t*)_index_bytes);
90 put_index(entry, i, (size_t*)_index_bytes);
6991 continue;
7092 }
7193 }
7294 }
7395
74
7596 switch (capacity_index_size(_indexes_len)) {
7697 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);
7798 case 2: return internal_put(key, value, (uint16_t*)_index_bytes);
......@@ -109,6 +130,16 @@ public:
109130
110131 bool maybe_remove(const K &key) {
111132 _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 }
112143 switch (capacity_index_size(_indexes_len)) {
113144 case 1: return internal_remove(key, (uint8_t*)_index_bytes);
114145 case 2: return internal_remove(key, (uint16_t*)_index_bytes);
......@@ -165,11 +196,16 @@ private:
165196 void init_capacity(size_t capacity) {
166197 _entries = {};
167198 _entries.ensure_capacity(capacity);
168 // So that at capacity it will only be 60% full.
169 _indexes_len = capacity * 5 / 3;
170 size_t sz = capacity_index_size(_indexes_len);
171 // This zero initializes _index_bytes which sets them all to empty.
172 _index_bytes = heap::c_allocator.allocate<uint8_t>(_indexes_len * sz);
199 _indexes_len = 0;
200 if (capacity >= 16) {
201 // So that at capacity it will only be 60% full.
202 _indexes_len = capacity * 5 / 3;
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
174210 _max_distance_from_start_index = 0;
175211 _modification_count = 0;
......@@ -187,47 +223,113 @@ private:
187223
188224 template <typename I>
189225 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)
226 uint32_t hash = HashFunction(key);
227 uint32_t distance_from_start_index = 0;
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)
193231 {
194232 size_t index_index = (start_index + roll_over) % _indexes_len;
195233 I index_data = indexes[index_index];
196234 if (index_data == 0) {
197 _entries.append({key, value});
235 _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value });
198236 indexes[index_index] = _entries.length;
199237 if (distance_from_start_index > _max_distance_from_start_index)
200238 _max_distance_from_start_index = distance_from_start_index;
201239 return;
202240 }
241 // This pointer survives the following append because we call
242 // _entries.ensure_capacity before internal_put.
203243 Entry *entry = &_entries.items[index_data - 1];
204 if (EqualFn(entry->key, key)) {
205 *entry = {key, value};
244 if (entry->hash == hash && EqualFn(entry->key, key)) {
245 *entry = {hash, distance_from_start_index, key, value};
206246 if (distance_from_start_index > _max_distance_from_start_index)
207247 _max_distance_from_start_index = distance_from_start_index;
208248 return;
209249 }
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 }
210289 }
211290 zig_unreachable();
212291 }
213292
214293 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;
216297 for (size_t roll_over = 0, distance_from_start_index = 0;
217298 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)
218299 {
219300 size_t index_index = (start_index + roll_over) % _indexes_len;
220 if (indexes[index_index] == 0) {
221 indexes[index_index] = entry_index + 1;
301 size_t next_index_data = indexes[index_index];
302 if (next_index_data == 0) {
222303 if (distance_from_start_index > _max_distance_from_start_index)
223304 _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;
224307 return;
225308 }
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 }
226319 }
227320 zig_unreachable();
228321 }
229322
230323 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 }
231333 switch (capacity_index_size(_indexes_len)) {
232334 case 1: return internal_get2(key, (uint8_t*)_index_bytes);
233335 case 2: return internal_get2(key, (uint16_t*)_index_bytes);
......@@ -238,7 +340,8 @@ private:
238340
239341 template <typename I>
240342 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);
242345 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
243346 size_t index_index = (start_index + roll_over) % _indexes_len;
244347 size_t index_data = indexes[index_index];
......@@ -246,19 +349,20 @@ private:
246349 return nullptr;
247350
248351 Entry *entry = &_entries.items[index_data - 1];
249 if (EqualFn(entry->key, key))
352 if (entry->hash == hash && EqualFn(entry->key, key))
250353 return entry;
251354 }
252355 return nullptr;
253356 }
254357
255 size_t key_to_index(const K &key) const {
256 return ((size_t)HashFunction(key)) % _indexes_len;
358 size_t hash_to_index(uint32_t hash) const {
359 return ((size_t)hash) % _indexes_len;
257360 }
258361
259362 template <typename I>
260363 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);
262366 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
263367 size_t index_index = (start_index + roll_over) % _indexes_len;
264368 size_t index_data = indexes[index_index];
......@@ -267,10 +371,10 @@ private:
267371
268372 size_t index = index_data - 1;
269373 Entry *entry = &_entries.items[index];
270 if (!EqualFn(entry->key, key))
374 if (entry->hash != hash || !EqualFn(entry->key, key))
271375 continue;
272376
273 indexes[index_index] = 0;
377 size_t prev_index = index_index;
274378 _entries.swap_remove(index);
275379 if (_entries.length > 0 && _entries.length != index) {
276380 // Because of the swap remove, now we need to update the index that was
......@@ -280,24 +384,29 @@ private:
280384
281385 // Now we have to shift over the following indexes.
282386 roll_over += 1;
283 for (; roll_over <= _max_distance_from_start_index; roll_over += 1) {
387 for (; roll_over < _indexes_len; roll_over += 1) {
284388 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];
389 if (indexes[next_index] == 0) {
390 indexes[prev_index] = 0;
391 return true;
392 }
393 Entry *next_entry = &_entries.items[indexes[next_index] - 1];
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;
291401 }
292
293 return true;
402 zig_unreachable();
294403 }
295404 return false;
296405 }
297406
298407 template <typename I>
299408 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);
301410 for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
302411 size_t index_index = (start_index + roll_over) % _indexes_len;
303412 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 {