authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-01 10:14:27+00:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-02 04:53:26+00:00
log8b82c40104802c9351b30ef7c5a41e7829ab6d2a
tree8ba2c9c73e5c53db9c902a204ecb179539703dff
parent6f98ef09e31768e3356598ef30e60fe028a0e70c

stage1: reimplement HashMap

The indexes are stored separately using an array of uint8_t, uint16_t, uint32_t, or size_t, depending on the number of entries in the map. Entries only contain a key and a value, no longer have distance_from_start_index or is_used. In theory this should be both faster and use less memory. In practice it seems to have little to no effect. For the standard library tests, vs master branch, the time had no discernable difference, and it shaved off only 13 MiB of peak rss usage.

2 files changed, 199 insertions(+), 131 deletions(-)

src/hash_map.hpp+198-129
...@@ -19,45 +19,64 @@ public:...@@ -19,45 +19,64 @@ public:
19 init_capacity(capacity);19 init_capacity(capacity);
20 }20 }
21 void deinit(void) {21 void deinit(void) {
22 heap::c_allocator.deallocate(_entries, _capacity);22 _entries.deinit();
23 heap::c_allocator.deallocate(_index_bytes,
24 _indexes_len * capacity_index_size(_indexes_len));
23 }25 }
2426
25 struct Entry {27 struct Entry {
26 K key;28 K key;
27 V value;29 V value;
28 bool used;
29 int distance_from_start_index;
30 };30 };
3131
32 void clear() {32 void clear() {
33 for (int i = 0; i < _capacity; i += 1) {33 _entries.clear();
34 _entries[i].used = false;34 memset(_index_bytes, 0, _indexes_len * capacity_index_size(_indexes_len));
35 }
36 _size = 0;
37 _max_distance_from_start_index = 0;35 _max_distance_from_start_index = 0;
38 _modification_count += 1;36 _modification_count += 1;
39 }37 }
4038
41 int size() const {39 size_t size() const {
42 return _size;40 return _entries.length;
43 }41 }
4442
45 void put(const K &key, const V &value) {43 void put(const K &key, const V &value) {
46 _modification_count += 1;44 _modification_count += 1;
47 internal_put(key, value);45
4846 // if we would get too full (60%), double the indexes size
49 // if we get too full (60%), double the capacity47 if ((_entries.length + 1) * 5 >= _indexes_len * 3) {
50 if (_size * 5 >= _capacity * 3) {48 heap::c_allocator.deallocate(_index_bytes,
51 Entry *old_entries = _entries;49 _indexes_len * capacity_index_size(_indexes_len));
52 int old_capacity = _capacity;50 _indexes_len *= 2;
53 init_capacity(_capacity * 2);51 size_t sz = capacity_index_size(_indexes_len);
54 // dump all of the old elements into the new table52 // This zero initializes the bytes, setting them all empty.
55 for (int i = 0; i < old_capacity; i += 1) {53 _index_bytes = heap::c_allocator.allocate<uint8_t>(_indexes_len * sz);
56 Entry *old_entry = &old_entries[i];54 _max_distance_from_start_index = 0;
57 if (old_entry->used)55 for (size_t i = 0; i < _entries.length; i += 1) {
58 internal_put(old_entry->key, old_entry->value);56 Entry *entry = &_entries.items[i];
57 switch (sz) {
58 case 1:
59 put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes);
60 continue;
61 case 2:
62 put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes);
63 continue;
64 case 4:
65 put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes);
66 continue;
67 default:
68 put_index(key_to_index(entry->key), i, (size_t*)_index_bytes);
69 continue;
70 }
59 }71 }
60 heap::c_allocator.deallocate(old_entries, old_capacity);72 }
73
74
75 switch (capacity_index_size(_indexes_len)) {
76 case 1: return internal_put(key, value, (uint8_t*)_index_bytes);
77 case 2: return internal_put(key, value, (uint16_t*)_index_bytes);
78 case 4: return internal_put(key, value, (uint32_t*)_index_bytes);
79 default: return internal_put(key, value, (size_t*)_index_bytes);
61 }80 }
62 }81 }
6382
...@@ -81,40 +100,21 @@ public:...@@ -81,40 +100,21 @@ public:
81 return internal_get(key);100 return internal_get(key);
82 }101 }
83102
84 void maybe_remove(const K &key) {103 bool remove(const K &key) {
85 if (maybe_get(key)) {104 bool deleted_something = maybe_remove(key);
86 remove(key);105 if (!deleted_something)
87 }106 zig_panic("key not found");
107 return deleted_something;
88 }108 }
89109
90 void remove(const K &key) {110 bool maybe_remove(const K &key) {
91 _modification_count += 1;111 _modification_count += 1;
92 int start_index = key_to_index(key);112 switch (capacity_index_size(_indexes_len)) {
93 for (int roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {113 case 1: return internal_remove(key, (uint8_t*)_index_bytes);
94 int index = (start_index + roll_over) % _capacity;114 case 2: return internal_remove(key, (uint16_t*)_index_bytes);
95 Entry *entry = &_entries[index];115 case 4: return internal_remove(key, (uint32_t*)_index_bytes);
96116 default: return internal_remove(key, (size_t*)_index_bytes);
97 if (!entry->used)
98 zig_panic("key not found");
99
100 if (!EqualFn(entry->key, key))
101 continue;
102
103 for (; roll_over < _capacity; roll_over += 1) {
104 int next_index = (start_index + roll_over + 1) % _capacity;
105 Entry *next_entry = &_entries[next_index];
106 if (!next_entry->used || next_entry->distance_from_start_index == 0) {
107 entry->used = false;
108 _size -= 1;
109 return;
110 }
111 *entry = *next_entry;
112 entry->distance_from_start_index -= 1;
113 entry = next_entry;
114 }
115 zig_panic("shifting everything in the table");
116 }117 }
117 zig_panic("key not found");
118 }118 }
119119
120 class Iterator {120 class Iterator {
...@@ -122,24 +122,16 @@ public:...@@ -122,24 +122,16 @@ public:
122 Entry *next() {122 Entry *next() {
123 if (_inital_modification_count != _table->_modification_count)123 if (_inital_modification_count != _table->_modification_count)
124 zig_panic("concurrent modification");124 zig_panic("concurrent modification");
125 if (_count >= _table->size())125 if (_index >= _table->_entries.length)
126 return NULL;126 return nullptr;
127 for (; _index < _table->_capacity; _index += 1) {127 Entry *entry = &_table->_entries.items[_index];
128 Entry *entry = &_table->_entries[_index];128 _index += 1;
129 if (entry->used) {129 return entry;
130 _index += 1;
131 _count += 1;
132 return entry;
133 }
134 }
135 zig_panic("no next item");
136 }130 }
137 private:131 private:
138 const HashMap * _table;132 const HashMap * _table;
139 // how many items have we returned
140 int _count = 0;
141 // iterator through the entry array133 // iterator through the entry array
142 int _index = 0;134 size_t _index = 0;
143 // used to detect concurrent modification135 // used to detect concurrent modification
144 uint32_t _inital_modification_count;136 uint32_t _inital_modification_count;
145 Iterator(const HashMap * table) :137 Iterator(const HashMap * table) :
...@@ -154,89 +146,166 @@ public:...@@ -154,89 +146,166 @@ public:
154 }146 }
155147
156private:148private:
157149 // Maintains insertion order.
158 Entry *_entries;150 ZigList<Entry> _entries;
159 int _capacity;151 // If _indexes_len is less than 2**8, this is an array of uint8_t.
160 int _size;152 // If _indexes_len is less than 2**16, it is an array of uint16_t.
161 int _max_distance_from_start_index;153 // If _indexes_len is less than 2**32, it is an array of uint32_t.
162 // this is used to detect bugs where a hashtable is edited while an iterator is running.154 // Otherwise it is size_t.
155 // It's off by 1. 0 means empty slot, 1 means index 0, etc.
156 uint8_t *_index_bytes;
157 // This is the number of indexes. When indexes are bytes, it equals number of bytes.
158 // When indexes are uint16_t, _indexes_len is half the number of bytes.
159 size_t _indexes_len;
160
161 size_t _max_distance_from_start_index;
162 // This is used to detect bugs where a hashtable is edited while an iterator is running.
163 uint32_t _modification_count;163 uint32_t _modification_count;
164164
165 void init_capacity(int capacity) {165 void init_capacity(size_t capacity) {
166 _capacity = capacity;166 _entries = {};
167 _entries = heap::c_allocator.allocate<Entry>(_capacity);167 _entries.ensure_capacity(capacity);
168 _size = 0;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);
173
169 _max_distance_from_start_index = 0;174 _max_distance_from_start_index = 0;
170 for (int i = 0; i < _capacity; i += 1) {175 _modification_count = 0;
171 _entries[i].used = false;176 }
172 }177
178 static size_t capacity_index_size(size_t len) {
179 if (len < UINT8_MAX)
180 return 1;
181 if (len < UINT16_MAX)
182 return 2;
183 if (len < UINT32_MAX)
184 return 4;
185 return sizeof(size_t);
173 }186 }
174187
175 void internal_put(K key, V value) {188 template <typename I>
176 int start_index = key_to_index(key);189 void internal_put(const K &key, const V &value, I *indexes) {
177 for (int roll_over = 0, distance_from_start_index = 0;190 size_t start_index = key_to_index(key);
178 roll_over < _capacity; roll_over += 1, distance_from_start_index += 1)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)
179 {193 {
180 int index = (start_index + roll_over) % _capacity;194 size_t index_index = (start_index + roll_over) % _indexes_len;
181 Entry *entry = &_entries[index];195 I index_data = indexes[index_index];
182196 if (index_data == 0) {
183 if (entry->used && !EqualFn(entry->key, key)) {197 _entries.append({key, value});
184 if (entry->distance_from_start_index < distance_from_start_index) {198 indexes[index_index] = _entries.length;
185 // robin hood to the rescue199 if (distance_from_start_index > _max_distance_from_start_index)
186 Entry tmp = *entry;200 _max_distance_from_start_index = distance_from_start_index;
187 if (distance_from_start_index > _max_distance_from_start_index)201 return;
188 _max_distance_from_start_index = distance_from_start_index;
189 *entry = {
190 key,
191 value,
192 true,
193 distance_from_start_index,
194 };
195 key = tmp.key;
196 value = tmp.value;
197 distance_from_start_index = tmp.distance_from_start_index;
198 }
199 continue;
200 }202 }
201203 Entry *entry = &_entries.items[index_data - 1];
202 if (!entry->used) {204 if (EqualFn(entry->key, key)) {
203 // adding an entry. otherwise overwriting old value with205 *entry = {key, value};
204 // same key206 if (distance_from_start_index > _max_distance_from_start_index)
205 _size += 1;207 _max_distance_from_start_index = distance_from_start_index;
208 return;
206 }209 }
207
208 if (distance_from_start_index > _max_distance_from_start_index)
209 _max_distance_from_start_index = distance_from_start_index;
210 *entry = {
211 key,
212 value,
213 true,
214 distance_from_start_index,
215 };
216 return;
217 }210 }
218 zig_panic("put into a full HashMap");211 zig_unreachable();
219 }212 }
220213
214 template <typename I>
215 void put_index(size_t start_index, size_t entry_index, I *indexes) {
216 for (size_t roll_over = 0, distance_from_start_index = 0;
217 roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1)
218 {
219 size_t index_index = (start_index + roll_over) % _indexes_len;
220 if (indexes[index_index] == 0) {
221 indexes[index_index] = entry_index + 1;
222 if (distance_from_start_index > _max_distance_from_start_index)
223 _max_distance_from_start_index = distance_from_start_index;
224 return;
225 }
226 }
227 zig_unreachable();
228 }
221229
222 Entry *internal_get(const K &key) const {230 Entry *internal_get(const K &key) const {
223 int start_index = key_to_index(key);231 switch (capacity_index_size(_indexes_len)) {
224 for (int roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {232 case 1: return internal_get2(key, (uint8_t*)_index_bytes);
225 int index = (start_index + roll_over) % _capacity;233 case 2: return internal_get2(key, (uint16_t*)_index_bytes);
226 Entry *entry = &_entries[index];234 case 4: return internal_get2(key, (uint32_t*)_index_bytes);
235 default: return internal_get2(key, (size_t*)_index_bytes);
236 }
237 }
227238
228 if (!entry->used)239 template <typename I>
229 return NULL;240 Entry *internal_get2(const K &key, I *indexes) const {
241 size_t start_index = key_to_index(key);
242 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;
244 size_t index_data = indexes[index_index];
245 if (index_data == 0)
246 return nullptr;
230247
248 Entry *entry = &_entries.items[index_data - 1];
231 if (EqualFn(entry->key, key))249 if (EqualFn(entry->key, key))
232 return entry;250 return entry;
233 }251 }
234 return NULL;252 return nullptr;
235 }253 }
236254
237 int key_to_index(const K &key) const {255 size_t key_to_index(const K &key) const {
238 return (int)(HashFunction(key) % ((uint32_t)_capacity));256 return ((size_t)HashFunction(key)) % _indexes_len;
257 }
258
259 template <typename I>
260 bool internal_remove(const K &key, I *indexes) {
261 size_t start_index = key_to_index(key);
262 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;
264 size_t index_data = indexes[index_index];
265 if (index_data == 0)
266 return false;
267
268 size_t index = index_data - 1;
269 Entry *entry = &_entries.items[index];
270 if (!EqualFn(entry->key, key))
271 continue;
272
273 indexes[index_index] = 0;
274 _entries.swap_remove(index);
275 if (_entries.length > 0 && _entries.length != index) {
276 // Because of the swap remove, now we need to update the index that was
277 // pointing to the last entry and is now pointing to this removed item slot.
278 update_entry_index(_entries.length, index, indexes);
279 }
280
281 // Now we have to shift over the following indexes.
282 roll_over += 1;
283 for (; roll_over <= _max_distance_from_start_index; roll_over += 1) {
284 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];
291 }
292
293 return true;
294 }
295 return false;
239 }296 }
240};
241297
298 template <typename I>
299 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);
301 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;
303 if (indexes[index_index] == old_entry_index + 1) {
304 indexes[index_index] = new_entry_index + 1;
305 return;
306 }
307 }
308 zig_unreachable();
309 }
310};
242#endif311#endif
test/stage1/behavior/type_info.zig+1-2
...@@ -251,14 +251,13 @@ fn testStruct() void {...@@ -251,14 +251,13 @@ fn testStruct() void {
251}251}
252252
253const TestStruct = packed struct {253const TestStruct = packed struct {
254 const Self = @This();
255
256 fieldA: usize,254 fieldA: usize,
257 fieldB: void,255 fieldB: void,
258 fieldC: *Self,256 fieldC: *Self,
259 fieldD: u32 = 4,257 fieldD: u32 = 4,
260258
261 pub fn foo(self: *const Self) void {}259 pub fn foo(self: *const Self) void {}
260 const Self = @This();
262};261};
263262
264test "type info: function type info" {263test "type info: function type info" {