authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-02 17:34:12-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-10 20:51:09-07:00
logab86b2024883f67c0fa06108f66e4e88b98c3163
tree137536a6bf4d5c089992a1c38604e5c9f1d9b551
parent7c12e064c4e6cd7ea2243a665984e5c49bc94229

std.hash: improve small-key hashing in Wyhash

Instead of carrying an optimized version of wyhash in the compiler for small keys, put it into the std lib where it belongs. ...except it does not match the official test cases. This will need to be fixed before merging into master. This is an extremely contributor-friendly task. Related issue: #15916

2 files changed, 22 insertions(+), 107 deletions(-)

lib/std/hash/benchmark.zig-10
...@@ -38,16 +38,6 @@ const hashes = [_]Hash{...@@ -38,16 +38,6 @@ const hashes = [_]Hash{
38 .name = "wyhash",38 .name = "wyhash",
39 .init_u64 = 0,39 .init_u64 = 0,
40 },40 },
41 Hash{
42 .ty = hash.XxHash64,
43 .name = "xxhash64",
44 .init_u64 = 0,
45 },
46 Hash{
47 .ty = hash.XxHash32,
48 .name = "xxhash32",
49 .init_u64 = 0,
50 },
51 Hash{41 Hash{
52 .ty = hash.Fnv1a_64,42 .ty = hash.Fnv1a_64,
53 .name = "fnv1a",43 .name = "fnv1a",
src/InternPool.zig+22-97
...@@ -69,6 +69,7 @@ const assert = std.debug.assert;...@@ -69,6 +69,7 @@ const assert = std.debug.assert;
69const BigIntConst = std.math.big.int.Const;69const BigIntConst = std.math.big.int.Const;
70const BigIntMutable = std.math.big.int.Mutable;70const BigIntMutable = std.math.big.int.Mutable;
71const Limb = std.math.big.Limb;71const Limb = std.math.big.Limb;
72const Hash = std.hash.Wyhash;
7273
73const InternPool = @This();74const InternPool = @This();
74const Module = @import("Module.zig");75const Module = @import("Module.zig");
...@@ -675,34 +676,34 @@ pub const Key = union(enum) {...@@ -675,34 +676,34 @@ pub const Key = union(enum) {
675 .empty_enum_value,676 .empty_enum_value,
676 .inferred_error_set_type,677 .inferred_error_set_type,
677 .un,678 .un,
678 => |x| WyhashKing.hash(seed, asBytes(&x)),679 => |x| Hash.hash(seed, asBytes(&x)),
679680
680 .int_type => |x| WyhashKing.hash(seed + @enumToInt(x.signedness), asBytes(&x.bits)),681 .int_type => |x| Hash.hash(seed + @enumToInt(x.signedness), asBytes(&x.bits)),
681 .union_type => |x| WyhashKing.hash(seed + @enumToInt(x.runtime_tag), asBytes(&x.index)),682 .union_type => |x| Hash.hash(seed + @enumToInt(x.runtime_tag), asBytes(&x.index)),
682683
683 .error_union => |x| switch (x.val) {684 .error_union => |x| switch (x.val) {
684 .err_name => |y| WyhashKing.hash(seed + 0, asBytes(&x.ty) ++ asBytes(&y)),685 .err_name => |y| Hash.hash(seed + 0, asBytes(&x.ty) ++ asBytes(&y)),
685 .payload => |y| WyhashKing.hash(seed + 1, asBytes(&x.ty) ++ asBytes(&y)),686 .payload => |y| Hash.hash(seed + 1, asBytes(&x.ty) ++ asBytes(&y)),
686 },687 },
687688
688 .runtime_value => |x| WyhashKing.hash(seed, asBytes(&x.val)),689 .runtime_value => |x| Hash.hash(seed, asBytes(&x.val)),
689 .opaque_type => |x| WyhashKing.hash(seed, asBytes(&x.decl)),690 .opaque_type => |x| Hash.hash(seed, asBytes(&x.decl)),
690691
691 .enum_type => |enum_type| {692 .enum_type => |enum_type| {
692 var hasher = std.hash.Wyhash.init(seed);693 var hasher = Hash.init(seed);
693 std.hash.autoHash(&hasher, enum_type.decl);694 std.hash.autoHash(&hasher, enum_type.decl);
694 return hasher.final();695 return hasher.final();
695 },696 },
696697
697 .variable => |variable| {698 .variable => |variable| {
698 var hasher = std.hash.Wyhash.init(seed);699 var hasher = Hash.init(seed);
699 std.hash.autoHash(&hasher, variable.decl);700 std.hash.autoHash(&hasher, variable.decl);
700 return hasher.final();701 return hasher.final();
701 },702 },
702 .extern_func => |x| WyhashKing.hash(seed, asBytes(&x.ty) ++ asBytes(&x.decl)),703 .extern_func => |x| Hash.hash(seed, asBytes(&x.ty) ++ asBytes(&x.decl)),
703704
704 .int => |int| {705 .int => |int| {
705 var hasher = std.hash.Wyhash.init(seed);706 var hasher = Hash.init(seed);
706 // Canonicalize all integers by converting them to BigIntConst.707 // Canonicalize all integers by converting them to BigIntConst.
707 switch (int.storage) {708 switch (int.storage) {
708 .u64, .i64, .big_int => {709 .u64, .i64, .big_int => {
...@@ -725,7 +726,7 @@ pub const Key = union(enum) {...@@ -725,7 +726,7 @@ pub const Key = union(enum) {
725 },726 },
726727
727 .float => |float| {728 .float => |float| {
728 var hasher = std.hash.Wyhash.init(seed);729 var hasher = Hash.init(seed);
729 std.hash.autoHash(&hasher, float.ty);730 std.hash.autoHash(&hasher, float.ty);
730 switch (float.storage) {731 switch (float.storage) {
731 inline else => |val| std.hash.autoHash(732 inline else => |val| std.hash.autoHash(
...@@ -743,19 +744,19 @@ pub const Key = union(enum) {...@@ -743,19 +744,19 @@ pub const Key = union(enum) {
743 const seed2 = seed + @enumToInt(addr);744 const seed2 = seed + @enumToInt(addr);
744 const common = asBytes(&ptr.ty) ++ asBytes(&ptr.len);745 const common = asBytes(&ptr.ty) ++ asBytes(&ptr.len);
745 return switch (ptr.addr) {746 return switch (ptr.addr) {
746 .decl => |x| WyhashKing.hash(seed2, common ++ asBytes(&x)),747 .decl => |x| Hash.hash(seed2, common ++ asBytes(&x)),
747748
748 .mut_decl => |x| WyhashKing.hash(749 .mut_decl => |x| Hash.hash(
749 seed2,750 seed2,
750 asBytes(&x.decl) ++ asBytes(&x.runtime_index),751 asBytes(&x.decl) ++ asBytes(&x.runtime_index),
751 ),752 ),
752753
753 .int, .eu_payload, .opt_payload, .comptime_field => |int| WyhashKing.hash(754 .int, .eu_payload, .opt_payload, .comptime_field => |int| Hash.hash(
754 seed2,755 seed2,
755 asBytes(&int),756 asBytes(&int),
756 ),757 ),
757758
758 .elem, .field => |x| WyhashKing.hash(759 .elem, .field => |x| Hash.hash(
759 seed2,760 seed2,
760 asBytes(&x.base) ++ asBytes(&x.index),761 asBytes(&x.base) ++ asBytes(&x.index),
761 ),762 ),
...@@ -763,7 +764,7 @@ pub const Key = union(enum) {...@@ -763,7 +764,7 @@ pub const Key = union(enum) {
763 },764 },
764765
765 .aggregate => |aggregate| {766 .aggregate => |aggregate| {
766 var hasher = std.hash.Wyhash.init(seed);767 var hasher = Hash.init(seed);
767 std.hash.autoHash(&hasher, aggregate.ty);768 std.hash.autoHash(&hasher, aggregate.ty);
768 const len = ip.aggregateTypeLen(aggregate.ty);769 const len = ip.aggregateTypeLen(aggregate.ty);
769 const child = switch (ip.indexToKey(aggregate.ty)) {770 const child = switch (ip.indexToKey(aggregate.ty)) {
...@@ -823,13 +824,13 @@ pub const Key = union(enum) {...@@ -823,13 +824,13 @@ pub const Key = union(enum) {
823 },824 },
824825
825 .error_set_type => |error_set_type| {826 .error_set_type => |error_set_type| {
826 var hasher = std.hash.Wyhash.init(seed);827 var hasher = Hash.init(seed);
827 for (error_set_type.names) |elem| std.hash.autoHash(&hasher, elem);828 for (error_set_type.names) |elem| std.hash.autoHash(&hasher, elem);
828 return hasher.final();829 return hasher.final();
829 },830 },
830831
831 .anon_struct_type => |anon_struct_type| {832 .anon_struct_type => |anon_struct_type| {
832 var hasher = std.hash.Wyhash.init(seed);833 var hasher = Hash.init(seed);
833 for (anon_struct_type.types) |elem| std.hash.autoHash(&hasher, elem);834 for (anon_struct_type.types) |elem| std.hash.autoHash(&hasher, elem);
834 for (anon_struct_type.values) |elem| std.hash.autoHash(&hasher, elem);835 for (anon_struct_type.values) |elem| std.hash.autoHash(&hasher, elem);
835 for (anon_struct_type.names) |elem| std.hash.autoHash(&hasher, elem);836 for (anon_struct_type.names) |elem| std.hash.autoHash(&hasher, elem);
...@@ -837,7 +838,7 @@ pub const Key = union(enum) {...@@ -837,7 +838,7 @@ pub const Key = union(enum) {
837 },838 },
838839
839 .func_type => |func_type| {840 .func_type => |func_type| {
840 var hasher = std.hash.Wyhash.init(seed);841 var hasher = Hash.init(seed);
841 for (func_type.param_types) |param_type| std.hash.autoHash(&hasher, param_type);842 for (func_type.param_types) |param_type| std.hash.autoHash(&hasher, param_type);
842 std.hash.autoHash(&hasher, func_type.return_type);843 std.hash.autoHash(&hasher, func_type.return_type);
843 std.hash.autoHash(&hasher, func_type.comptime_bits);844 std.hash.autoHash(&hasher, func_type.comptime_bits);
...@@ -851,7 +852,7 @@ pub const Key = union(enum) {...@@ -851,7 +852,7 @@ pub const Key = union(enum) {
851 },852 },
852853
853 .memoized_call => |memoized_call| {854 .memoized_call => |memoized_call| {
854 var hasher = std.hash.Wyhash.init(seed);855 var hasher = Hash.init(seed);
855 std.hash.autoHash(&hasher, memoized_call.func);856 std.hash.autoHash(&hasher, memoized_call.func);
856 for (memoized_call.arg_values) |arg| std.hash.autoHash(&hasher, arg);857 for (memoized_call.arg_values) |arg| std.hash.autoHash(&hasher, arg);
857 return hasher.final();858 return hasher.final();
...@@ -5744,79 +5745,3 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois...@@ -5744,79 +5745,3 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois
5744 .none => unreachable, // special tag5745 .none => unreachable, // special tag
5745 };5746 };
5746}5747}
5747
5748/// I got this from King, using this temporarily until std lib hashing can be
5749/// improved to make stateless hashing performant. Currently the
5750/// implementations suffer from not special casing small lengths and not taking
5751/// advantage of comptime-known lengths, both of which this implementation
5752/// does.
5753const WyhashKing = struct {
5754 inline fn mum(pair: *[2]u64) void {
5755 const x = @as(u128, pair[0]) *% pair[1];
5756 pair[0] = @truncate(u64, x);
5757 pair[1] = @truncate(u64, x >> 64);
5758 }
5759
5760 inline fn mix(a: u64, b: u64) u64 {
5761 var pair = [_]u64{ a, b };
5762 mum(&pair);
5763 return pair[0] ^ pair[1];
5764 }
5765
5766 inline fn read(comptime I: type, in: []const u8) I {
5767 return std.mem.readIntLittle(I, in[0..@sizeOf(I)]);
5768 }
5769
5770 const secret = [_]u64{
5771 0xa0761d6478bd642f,
5772 0xe7037ed1a0b428db,
5773 0x8ebc6af09c88c6e3,
5774 0x589965cc75374cc3,
5775 };
5776
5777 fn hash(seed: u64, input: anytype) u64 {
5778 var in: []const u8 = input;
5779 var last = std.mem.zeroes([2]u64);
5780 const starting_len: u64 = input.len;
5781 var state = seed ^ mix(seed ^ secret[0], secret[1]);
5782
5783 if (in.len <= 16) {
5784 if (in.len >= 4) {
5785 const end = (in.len >> 3) << 2;
5786 last[0] = (@as(u64, read(u32, in)) << 32) | read(u32, in[end..]);
5787 last[1] = (@as(u64, read(u32, in[in.len - 4 ..])) << 32) | read(u32, in[in.len - 4 - end ..]);
5788 } else if (in.len > 0) {
5789 last[0] = (@as(u64, in[0]) << 16) | (@as(u64, in[in.len >> 1]) << 8) | in[in.len - 1];
5790 }
5791 } else {
5792 large: {
5793 if (in.len <= 48) break :large;
5794 var split = [_]u64{ state, state, state };
5795 while (true) {
5796 for (&split, 0..) |*lane, i| {
5797 const a = read(u64, in[(i * 2) * 8 ..]) ^ secret[i + 1];
5798 const b = read(u64, in[((i * 2) + 1) * 8 ..]) ^ lane.*;
5799 lane.* = mix(a, b);
5800 }
5801 in = in[48..];
5802 if (in.len > 48) continue;
5803 state = split[0] ^ (split[1] ^ split[2]);
5804 break :large;
5805 }
5806 }
5807 while (true) {
5808 if (in.len <= 16) break;
5809 state = mix(read(u64, in) ^ secret[1], read(u64, in[8..]) ^ state);
5810 in = in[16..];
5811 if (in.len <= 16) break;
5812 }
5813 last[0] = read(u64, in[in.len - 16 ..]);
5814 last[1] = read(u64, in[in.len - 8 ..]);
5815 }
5816
5817 last[0] ^= secret[1];
5818 last[1] ^= state;
5819 mum(&last);
5820 return mix(last[0] ^ secret[0] ^ starting_len, last[1] ^ secret[1]);
5821 }
5822};