authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-04 02:07:27+00:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-05 21:11:42+00:00
log3c8b13d998c5c58c8171d36d7506ea3a181d0db9
tree91a2b8fc6c54afd2564896dcf6b7b03dd3d56e5a
parent632acffcbd96a085ea92899e6f37465e40178f44

std hash map: do the pow2 improvement again

it's a noticeable speedup

1 files changed, 25 insertions(+), 21 deletions(-)

lib/std/hash_map.zig+25-21
......@@ -382,22 +382,24 @@ pub fn HashMapUnmanaged(
382382 try self.entries.ensureCapacity(allocator, new_capacity);
383383 if (new_capacity <= linear_scan_max) return;
384384
385 // Resize if indexes would be more than 60% full.
385 // Ensure that the indexes will be at most 60% full if
386 // `new_capacity` items are put into it.
386387 const needed_len = new_capacity * 5 / 3;
387388 if (self.index_header) |header| {
388389 if (needed_len > header.indexes_len) {
389 var new_indexes_len = header.indexes_len;
390 while (true) {
391 new_indexes_len *= new_indexes_len / 2 + 8;
392 if (new_indexes_len >= needed_len) break;
393 }
390 // An overflow here would mean the amount of memory required would not
391 // be representable in the address space.
392 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
394393 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);
395394 self.insertAllEntriesIntoNewHeader(new_header);
396395 header.free(allocator);
397396 self.index_header = new_header;
398397 }
399398 } else {
400 const header = try IndexHeader.alloc(allocator, needed_len);
399 // An overflow here would mean the amount of memory required would not
400 // be representable in the address space.
401 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
402 const header = try IndexHeader.alloc(allocator, new_indexes_len);
401403 self.insertAllEntriesIntoNewHeader(header);
402404 self.index_header = header;
403405 }
......@@ -540,10 +542,10 @@ pub fn HashMapUnmanaged(
540542 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {
541543 const indexes = header.indexes(I);
542544 const h = hash(key);
543 const start_index = header.hashToIndex(h);
545 const start_index = header.constrainIndex(h);
544546 var roll_over: usize = 0;
545547 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
546 const index_index = (start_index + roll_over) % header.indexes_len;
548 const index_index = header.constrainIndex(start_index + roll_over);
547549 var index = &indexes[index_index];
548550 if (index.isEmpty())
549551 return null;
......@@ -564,7 +566,7 @@ pub fn HashMapUnmanaged(
564566 // Now we have to shift over the following indexes.
565567 roll_over += 1;
566568 while (roll_over < header.indexes_len) : (roll_over += 1) {
567 const next_index_index = (start_index + roll_over) % header.indexes_len;
569 const next_index_index = header.constrainIndex(start_index + roll_over);
568570 const next_index = &indexes[next_index_index];
569571 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {
570572 index.setEmpty();
......@@ -588,10 +590,10 @@ pub fn HashMapUnmanaged(
588590 indexes: []Index(I),
589591 ) void {
590592 const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key);
591 const start_index = header.hashToIndex(h);
593 const start_index = header.constrainIndex(h);
592594 var roll_over: usize = 0;
593595 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
594 const index_index = (start_index + roll_over) % header.indexes_len;
596 const index_index = header.constrainIndex(start_index + roll_over);
595597 const index = &indexes[index_index];
596598 if (index.entry_index == old_entry_index) {
597599 index.entry_index = @intCast(I, new_entry_index);
......@@ -605,14 +607,14 @@ pub fn HashMapUnmanaged(
605607 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {
606608 const indexes = header.indexes(I);
607609 const h = hash(key);
608 const start_index = header.hashToIndex(h);
610 const start_index = header.constrainIndex(h);
609611 var roll_over: usize = 0;
610612 var distance_from_start_index: usize = 0;
611613 while (roll_over <= header.indexes_len) : ({
612614 roll_over += 1;
613615 distance_from_start_index += 1;
614616 }) {
615 const index_index = (start_index + roll_over) % header.indexes_len;
617 const index_index = header.constrainIndex(start_index + roll_over);
616618 const index = indexes[index_index];
617619 if (index.isEmpty()) {
618620 indexes[index_index] = .{
......@@ -670,7 +672,7 @@ pub fn HashMapUnmanaged(
670672 roll_over += 1;
671673 distance_from_start_index += 1;
672674 }) {
673 const next_index_index = (start_index + roll_over) % header.indexes_len;
675 const next_index_index = header.constrainIndex(start_index + roll_over);
674676 const next_index = indexes[next_index_index];
675677 if (next_index.isEmpty()) {
676678 header.maybeBumpMax(distance_from_start_index);
......@@ -702,10 +704,10 @@ pub fn HashMapUnmanaged(
702704 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
703705 const indexes = header.indexes(I);
704706 const h = hash(key);
705 const start_index = header.hashToIndex(h);
707 const start_index = header.constrainIndex(h);
706708 var roll_over: usize = 0;
707709 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
708 const index_index = (start_index + roll_over) % header.indexes_len;
710 const index_index = header.constrainIndex(start_index + roll_over);
709711 const index = indexes[index_index];
710712 if (index.isEmpty())
711713 return null;
......@@ -731,7 +733,7 @@ pub fn HashMapUnmanaged(
731733 const indexes = header.indexes(I);
732734 entry_loop: for (self.entries.items) |entry, i| {
733735 const h = if (store_hash) entry.hash else hash(entry.key);
734 const start_index = header.hashToIndex(h);
736 const start_index = header.constrainIndex(h);
735737 var entry_index = i;
736738 var roll_over: usize = 0;
737739 var distance_from_start_index: usize = 0;
......@@ -739,7 +741,7 @@ pub fn HashMapUnmanaged(
739741 roll_over += 1;
740742 distance_from_start_index += 1;
741743 }) {
742 const index_index = (start_index + roll_over) % header.indexes_len;
744 const index_index = header.constrainIndex(start_index + roll_over);
743745 const next_index = indexes[index_index];
744746 if (next_index.isEmpty()) {
745747 header.maybeBumpMax(distance_from_start_index);
......@@ -814,8 +816,10 @@ const IndexHeader = struct {
814816 max_distance_from_start_index: usize,
815817 indexes_len: usize,
816818
817 fn hashToIndex(header: IndexHeader, h: u32) usize {
818 return @as(usize, h) % header.indexes_len;
819 fn constrainIndex(header: IndexHeader, i: usize) usize {
820 // This is an optimization for modulo of power of two integers;
821 // it requires `indexes_len` to always be a power of two.
822 return i & (header.indexes_len - 1);
819823 }
820824
821825 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {