| ... | @@ -139,9 +139,9 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -139,9 +139,9 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 139 | // ensure that the hash map will be at most 60% full if | 139 | // ensure that the hash map will be at most 60% full if |
| 140 | // expected_count items are put into it | 140 | // expected_count items are put into it |
| 141 | var optimized_capacity = expected_count * 5 / 3; | 141 | var optimized_capacity = expected_count * 5 / 3; |
| 142 | // round capacity to the next power of two | 142 | // an overflow here would mean the amount of memory required would not |
| 143 | const pow = math.log2_int_ceil(usize, optimized_capacity); | 143 | // be representable in the address space |
| 144 | return math.pow(usize, 2, pow); | 144 | return math.ceilPowerOfTwo(usize, optimized_capacity) catch unreachable; |
| 145 | } | 145 | } |
| 146 | | 146 | |
| 147 | /// Increases capacity so that the hash map will be at most | 147 | /// Increases capacity so that the hash map will be at most |
| ... | @@ -155,6 +155,8 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -155,6 +155,8 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 155 | /// capacity is greater than the current capacity. | 155 | /// capacity is greater than the current capacity. |
| 156 | /// New capacity must be a power of two. | 156 | /// New capacity must be a power of two. |
| 157 | fn ensureCapacityExact(self: *Self, new_capacity: usize) !void { | 157 | fn ensureCapacityExact(self: *Self, new_capacity: usize) !void { |
| | 158 | // capacity must always be a power of two to allow for modulo |
| | 159 | // optimization in the constrainIndex fn |
| 158 | const is_power_of_two = new_capacity & (new_capacity - 1) == 0; | 160 | const is_power_of_two = new_capacity & (new_capacity - 1) == 0; |
| 159 | assert(is_power_of_two); | 161 | assert(is_power_of_two); |
| 160 | | 162 | |
| ... | @@ -209,7 +211,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -209,7 +211,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 209 | { | 211 | { |
| 210 | var roll_over: usize = 0; | 212 | var roll_over: usize = 0; |
| 211 | while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) { | 213 | while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) { |
| 212 | const index = (start_index + roll_over) % hm.entries.len; | 214 | const index = hm.constrainIndex(start_index + roll_over); |
| 213 | var entry = &hm.entries[index]; | 215 | var entry = &hm.entries[index]; |
| 214 | | 216 | |
| 215 | if (!entry.used) return null; | 217 | if (!entry.used) return null; |
| ... | @@ -218,7 +220,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -218,7 +220,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 218 | | 220 | |
| 219 | const removed_kv = entry.kv; | 221 | const removed_kv = entry.kv; |
| 220 | while (roll_over < hm.entries.len) : (roll_over += 1) { | 222 | while (roll_over < hm.entries.len) : (roll_over += 1) { |
| 221 | const next_index = (start_index + roll_over + 1) % hm.entries.len; | 223 | const next_index = hm.constrainIndex(start_index + roll_over + 1); |
| 222 | const next_entry = &hm.entries[next_index]; | 224 | const next_entry = &hm.entries[next_index]; |
| 223 | if (!next_entry.used or next_entry.distance_from_start_index == 0) { | 225 | if (!next_entry.used or next_entry.distance_from_start_index == 0) { |
| 224 | entry.used = false; | 226 | entry.used = false; |
| ... | @@ -301,7 +303,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -301,7 +303,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 301 | roll_over += 1; | 303 | roll_over += 1; |
| 302 | distance_from_start_index += 1; | 304 | distance_from_start_index += 1; |
| 303 | }) { | 305 | }) { |
| 304 | const index = (start_index + roll_over) % self.entries.len; | 306 | const index = self.constrainIndex(start_index + roll_over); |
| 305 | const entry = &self.entries[index]; | 307 | const entry = &self.entries[index]; |
| 306 | | 308 | |
| 307 | if (entry.used and !eql(entry.kv.key, key)) { | 309 | if (entry.used and !eql(entry.kv.key, key)) { |
| ... | @@ -358,7 +360,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -358,7 +360,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 358 | { | 360 | { |
| 359 | var roll_over: usize = 0; | 361 | var roll_over: usize = 0; |
| 360 | while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) { | 362 | while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) { |
| 361 | const index = (start_index + roll_over) % hm.entries.len; | 363 | const index = hm.constrainIndex(start_index + roll_over); |
| 362 | const entry = &hm.entries[index]; | 364 | const entry = &hm.entries[index]; |
| 363 | | 365 | |
| 364 | if (!entry.used) return null; | 366 | if (!entry.used) return null; |
| ... | @@ -369,7 +371,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -369,7 +371,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 369 | } | 371 | } |
| 370 | | 372 | |
| 371 | fn keyToIndex(hm: Self, key: K) usize { | 373 | fn keyToIndex(hm: Self, key: K) usize { |
| 372 | return usize(hash(key)) % hm.entries.len; | 374 | return hm.constrainIndex(usize(hash(key))); |
| | 375 | } |
| | 376 | |
| | 377 | fn constrainIndex(hm: Self, i: usize) usize { |
| | 378 | // this is an optimization for modulo of power of two integers; |
| | 379 | // it requires hm.entries.len to always be a power of two |
| | 380 | return i & (hm.entries.len - 1); |
| 373 | } | 381 | } |
| 374 | }; | 382 | }; |
| 375 | } | 383 | } |