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(...@@ -382,22 +382,24 @@ pub fn HashMapUnmanaged(
382 try self.entries.ensureCapacity(allocator, new_capacity);382 try self.entries.ensureCapacity(allocator, new_capacity);
383 if (new_capacity <= linear_scan_max) return;383 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.
386 const needed_len = new_capacity * 5 / 3;387 const needed_len = new_capacity * 5 / 3;
387 if (self.index_header) |header| {388 if (self.index_header) |header| {
388 if (needed_len > header.indexes_len) {389 if (needed_len > header.indexes_len) {
389 var new_indexes_len = header.indexes_len;390 // An overflow here would mean the amount of memory required would not
390 while (true) {391 // be representable in the address space.
391 new_indexes_len *= new_indexes_len / 2 + 8;392 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
392 if (new_indexes_len >= needed_len) break;
393 }
394 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);393 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);
395 self.insertAllEntriesIntoNewHeader(new_header);394 self.insertAllEntriesIntoNewHeader(new_header);
396 header.free(allocator);395 header.free(allocator);
397 self.index_header = new_header;396 self.index_header = new_header;
398 }397 }
399 } else {398 } 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);
401 self.insertAllEntriesIntoNewHeader(header);403 self.insertAllEntriesIntoNewHeader(header);
402 self.index_header = header;404 self.index_header = header;
403 }405 }
...@@ -540,10 +542,10 @@ pub fn HashMapUnmanaged(...@@ -540,10 +542,10 @@ pub fn HashMapUnmanaged(
540 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {542 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {
541 const indexes = header.indexes(I);543 const indexes = header.indexes(I);
542 const h = hash(key);544 const h = hash(key);
543 const start_index = header.hashToIndex(h);545 const start_index = header.constrainIndex(h);
544 var roll_over: usize = 0;546 var roll_over: usize = 0;
545 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {547 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);
547 var index = &indexes[index_index];549 var index = &indexes[index_index];
548 if (index.isEmpty())550 if (index.isEmpty())
549 return null;551 return null;
...@@ -564,7 +566,7 @@ pub fn HashMapUnmanaged(...@@ -564,7 +566,7 @@ pub fn HashMapUnmanaged(
564 // Now we have to shift over the following indexes.566 // Now we have to shift over the following indexes.
565 roll_over += 1;567 roll_over += 1;
566 while (roll_over < header.indexes_len) : (roll_over += 1) {568 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);
568 const next_index = &indexes[next_index_index];570 const next_index = &indexes[next_index_index];
569 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {571 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {
570 index.setEmpty();572 index.setEmpty();
...@@ -588,10 +590,10 @@ pub fn HashMapUnmanaged(...@@ -588,10 +590,10 @@ pub fn HashMapUnmanaged(
588 indexes: []Index(I),590 indexes: []Index(I),
589 ) void {591 ) void {
590 const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key);592 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);
592 var roll_over: usize = 0;594 var roll_over: usize = 0;
593 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {595 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);
595 const index = &indexes[index_index];597 const index = &indexes[index_index];
596 if (index.entry_index == old_entry_index) {598 if (index.entry_index == old_entry_index) {
597 index.entry_index = @intCast(I, new_entry_index);599 index.entry_index = @intCast(I, new_entry_index);
...@@ -605,14 +607,14 @@ pub fn HashMapUnmanaged(...@@ -605,14 +607,14 @@ pub fn HashMapUnmanaged(
605 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {607 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {
606 const indexes = header.indexes(I);608 const indexes = header.indexes(I);
607 const h = hash(key);609 const h = hash(key);
608 const start_index = header.hashToIndex(h);610 const start_index = header.constrainIndex(h);
609 var roll_over: usize = 0;611 var roll_over: usize = 0;
610 var distance_from_start_index: usize = 0;612 var distance_from_start_index: usize = 0;
611 while (roll_over <= header.indexes_len) : ({613 while (roll_over <= header.indexes_len) : ({
612 roll_over += 1;614 roll_over += 1;
613 distance_from_start_index += 1;615 distance_from_start_index += 1;
614 }) {616 }) {
615 const index_index = (start_index + roll_over) % header.indexes_len;617 const index_index = header.constrainIndex(start_index + roll_over);
616 const index = indexes[index_index];618 const index = indexes[index_index];
617 if (index.isEmpty()) {619 if (index.isEmpty()) {
618 indexes[index_index] = .{620 indexes[index_index] = .{
...@@ -670,7 +672,7 @@ pub fn HashMapUnmanaged(...@@ -670,7 +672,7 @@ pub fn HashMapUnmanaged(
670 roll_over += 1;672 roll_over += 1;
671 distance_from_start_index += 1;673 distance_from_start_index += 1;
672 }) {674 }) {
673 const next_index_index = (start_index + roll_over) % header.indexes_len;675 const next_index_index = header.constrainIndex(start_index + roll_over);
674 const next_index = indexes[next_index_index];676 const next_index = indexes[next_index_index];
675 if (next_index.isEmpty()) {677 if (next_index.isEmpty()) {
676 header.maybeBumpMax(distance_from_start_index);678 header.maybeBumpMax(distance_from_start_index);
...@@ -702,10 +704,10 @@ pub fn HashMapUnmanaged(...@@ -702,10 +704,10 @@ pub fn HashMapUnmanaged(
702 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {704 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
703 const indexes = header.indexes(I);705 const indexes = header.indexes(I);
704 const h = hash(key);706 const h = hash(key);
705 const start_index = header.hashToIndex(h);707 const start_index = header.constrainIndex(h);
706 var roll_over: usize = 0;708 var roll_over: usize = 0;
707 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {709 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);
709 const index = indexes[index_index];711 const index = indexes[index_index];
710 if (index.isEmpty())712 if (index.isEmpty())
711 return null;713 return null;
...@@ -731,7 +733,7 @@ pub fn HashMapUnmanaged(...@@ -731,7 +733,7 @@ pub fn HashMapUnmanaged(
731 const indexes = header.indexes(I);733 const indexes = header.indexes(I);
732 entry_loop: for (self.entries.items) |entry, i| {734 entry_loop: for (self.entries.items) |entry, i| {
733 const h = if (store_hash) entry.hash else hash(entry.key);735 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);
735 var entry_index = i;737 var entry_index = i;
736 var roll_over: usize = 0;738 var roll_over: usize = 0;
737 var distance_from_start_index: usize = 0;739 var distance_from_start_index: usize = 0;
...@@ -739,7 +741,7 @@ pub fn HashMapUnmanaged(...@@ -739,7 +741,7 @@ pub fn HashMapUnmanaged(
739 roll_over += 1;741 roll_over += 1;
740 distance_from_start_index += 1;742 distance_from_start_index += 1;
741 }) {743 }) {
742 const index_index = (start_index + roll_over) % header.indexes_len;744 const index_index = header.constrainIndex(start_index + roll_over);
743 const next_index = indexes[index_index];745 const next_index = indexes[index_index];
744 if (next_index.isEmpty()) {746 if (next_index.isEmpty()) {
745 header.maybeBumpMax(distance_from_start_index);747 header.maybeBumpMax(distance_from_start_index);
...@@ -814,8 +816,10 @@ const IndexHeader = struct {...@@ -814,8 +816,10 @@ const IndexHeader = struct {
814 max_distance_from_start_index: usize,816 max_distance_from_start_index: usize,
815 indexes_len: usize,817 indexes_len: usize,
816818
817 fn hashToIndex(header: IndexHeader, h: u32) usize {819 fn constrainIndex(header: IndexHeader, i: usize) usize {
818 return @as(usize, h) % header.indexes_len;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);
819 }823 }
820824
821 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {825 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {