authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2019-06-05 23:26:48-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2019-06-05 23:26:48-07:00
log656ac43735094f7cc6b4616cd95e0d427b0174a6
treea38992b496f6bb148f3010e5f1525d5a3d402d79
parenta0d66fa1e6e8b9620e4990a33c32b0db11469aec

std.HashMap: optimize indexing by avoiding modulo operator

x % y can be optimized if y is a power of two by doing x & (y-1) instead. HashMap already enforces power of two capacity, so we can take advantage of this optimization.

1 files changed, 13 insertions(+), 5 deletions(-)

std/hash_map.zig+13-5
......@@ -155,6 +155,8 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
155155 /// capacity is greater than the current capacity.
156156 /// New capacity must be a power of two.
157157 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
158160 const is_power_of_two = new_capacity & (new_capacity - 1) == 0;
159161 assert(is_power_of_two);
160162
......@@ -209,7 +211,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
209211 {
210212 var roll_over: usize = 0;
211213 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);
213215 var entry = &hm.entries[index];
214216
215217 if (!entry.used) return null;
......@@ -218,7 +220,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
218220
219221 const removed_kv = entry.kv;
220222 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);
222224 const next_entry = &hm.entries[next_index];
223225 if (!next_entry.used or next_entry.distance_from_start_index == 0) {
224226 entry.used = false;
......@@ -301,7 +303,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
301303 roll_over += 1;
302304 distance_from_start_index += 1;
303305 }) {
304 const index = (start_index + roll_over) % self.entries.len;
306 const index = self.constrainIndex(start_index + roll_over);
305307 const entry = &self.entries[index];
306308
307309 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
358360 {
359361 var roll_over: usize = 0;
360362 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);
362364 const entry = &hm.entries[index];
363365
364366 if (!entry.used) return null;
......@@ -369,7 +371,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
369371 }
370372
371373 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);
373381 }
374382 };
375383}