| author | |
| committer | |
| log | 8c99a5199324a1aabba698a5127e9ce1df294aa7 |
| tree | 47a49324921c442e74b8e44f8471dabe0e5878cc |
| parent | f01cb8cc16bd048adefdec5a426d5ef33dff9168 |
| parent | 54255ee32e1e6c83b04c3e5f2f1dd7e8aa5e0dd7 |
| signature |
hash algorithm improvements6 files changed, 525 insertions(+), 113 deletions(-)
std/hash.zig+10| ... | ... | @@ -1,6 +1,9 @@ |
| 1 | 1 | const adler = @import("hash/adler.zig"); |
| 2 | 2 | pub const Adler32 = adler.Adler32; |
| 3 | 3 | |
| 4 | const auto_hash = @import("hash/auto_hash.zig"); | |
| 5 | pub const autoHash = auto_hash.autoHash; | |
| 6 | ||
| 4 | 7 | // pub for polynomials + generic crc32 construction |
| 5 | 8 | pub const crc = @import("hash/crc.zig"); |
| 6 | 9 | pub const Crc32 = crc.Crc32; |
| ... | ... | @@ -16,6 +19,8 @@ pub const SipHash128 = siphash.SipHash128; |
| 16 | 19 | |
| 17 | 20 | pub const murmur = @import("hash/murmur.zig"); |
| 18 | 21 | pub const Murmur2_32 = murmur.Murmur2_32; |
| 22 | ||
| 23 | ||
| 19 | 24 | pub const Murmur2_64 = murmur.Murmur2_64; |
| 20 | 25 | pub const Murmur3_32 = murmur.Murmur3_32; |
| 21 | 26 | |
| ... | ... | @@ -23,11 +28,16 @@ pub const cityhash = @import("hash/cityhash.zig"); |
| 23 | 28 | pub const CityHash32 = cityhash.CityHash32; |
| 24 | 29 | pub const CityHash64 = cityhash.CityHash64; |
| 25 | 30 | |
| 31 | const wyhash = @import("hash/wyhash.zig"); | |
| 32 | pub const Wyhash = wyhash.Wyhash; | |
| 33 | ||
| 26 | 34 | test "hash" { |
| 27 | 35 | _ = @import("hash/adler.zig"); |
| 36 | _ = @import("hash/auto_hash.zig"); | |
| 28 | 37 | _ = @import("hash/crc.zig"); |
| 29 | 38 | _ = @import("hash/fnv.zig"); |
| 30 | 39 | _ = @import("hash/siphash.zig"); |
| 31 | 40 | _ = @import("hash/murmur.zig"); |
| 32 | 41 | _ = @import("hash/cityhash.zig"); |
| 42 | _ = @import("hash/wyhash.zig"); | |
| 33 | 43 | } |
std/hash/auto_hash.zig created+210| ... | ... | @@ -0,0 +1,210 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const mem = std.mem; | |
| 4 | const meta = std.meta; | |
| 5 | ||
| 6 | /// Provides generic hashing for any eligible type. | |
| 7 | /// Only hashes `key` itself, pointers are not followed. | |
| 8 | pub fn autoHash(hasher: var, key: var) void { | |
| 9 | const Key = @typeOf(key); | |
| 10 | switch (@typeInfo(Key)) { | |
| 11 | builtin.TypeId.NoReturn, | |
| 12 | builtin.TypeId.Opaque, | |
| 13 | builtin.TypeId.Undefined, | |
| 14 | builtin.TypeId.ArgTuple, | |
| 15 | builtin.TypeId.Void, | |
| 16 | builtin.TypeId.Null, | |
| 17 | builtin.TypeId.BoundFn, | |
| 18 | builtin.TypeId.ComptimeFloat, | |
| 19 | builtin.TypeId.ComptimeInt, | |
| 20 | builtin.TypeId.Type, | |
| 21 | builtin.TypeId.EnumLiteral, | |
| 22 | => @compileError("cannot hash this type"), | |
| 23 | ||
| 24 | // Help the optimizer see that hashing an int is easy by inlining! | |
| 25 | // TODO Check if the situation is better after #561 is resolved. | |
| 26 | builtin.TypeId.Int => @inlineCall(hasher.update, std.mem.asBytes(&key)), | |
| 27 | ||
| 28 | builtin.TypeId.Float => |info| autoHash(hasher, @bitCast(@IntType(false, info.bits), key)), | |
| 29 | ||
| 30 | builtin.TypeId.Bool => autoHash(hasher, @boolToInt(key)), | |
| 31 | builtin.TypeId.Enum => autoHash(hasher, @enumToInt(key)), | |
| 32 | builtin.TypeId.ErrorSet => autoHash(hasher, @errorToInt(key)), | |
| 33 | builtin.TypeId.Promise, builtin.TypeId.Fn => autoHash(hasher, @ptrToInt(key)), | |
| 34 | ||
| 35 | builtin.TypeId.Pointer => |info| switch (info.size) { | |
| 36 | builtin.TypeInfo.Pointer.Size.One, | |
| 37 | builtin.TypeInfo.Pointer.Size.Many, | |
| 38 | builtin.TypeInfo.Pointer.Size.C, | |
| 39 | => autoHash(hasher, @ptrToInt(key)), | |
| 40 | ||
| 41 | builtin.TypeInfo.Pointer.Size.Slice => { | |
| 42 | autoHash(hasher, key.ptr); | |
| 43 | autoHash(hasher, key.len); | |
| 44 | }, | |
| 45 | }, | |
| 46 | ||
| 47 | builtin.TypeId.Optional => if (key) |k| autoHash(hasher, k), | |
| 48 | ||
| 49 | builtin.TypeId.Array => { | |
| 50 | // TODO detect via a trait when Key has no padding bits to | |
| 51 | // hash it as an array of bytes. | |
| 52 | // Otherwise, hash every element. | |
| 53 | for (key) |element| { | |
| 54 | autoHash(hasher, element); | |
| 55 | } | |
| 56 | }, | |
| 57 | ||
| 58 | builtin.TypeId.Vector => |info| { | |
| 59 | if (info.child.bit_count % 8 == 0) { | |
| 60 | // If there's no unused bits in the child type, we can just hash | |
| 61 | // this as an array of bytes. | |
| 62 | hasher.update(mem.asBytes(&key)); | |
| 63 | } else { | |
| 64 | // Otherwise, hash every element. | |
| 65 | // TODO remove the copy to an array once field access is done. | |
| 66 | const array: [info.len]info.child = key; | |
| 67 | comptime var i: u32 = 0; | |
| 68 | inline while (i < info.len) : (i += 1) { | |
| 69 | autoHash(hasher, array[i]); | |
| 70 | } | |
| 71 | } | |
| 72 | }, | |
| 73 | ||
| 74 | builtin.TypeId.Struct => |info| { | |
| 75 | // TODO detect via a trait when Key has no padding bits to | |
| 76 | // hash it as an array of bytes. | |
| 77 | // Otherwise, hash every field. | |
| 78 | inline for (info.fields) |field| { | |
| 79 | // We reuse the hash of the previous field as the seed for the | |
| 80 | // next one so that they're dependant. | |
| 81 | autoHash(hasher, @field(key, field.name)); | |
| 82 | } | |
| 83 | }, | |
| 84 | ||
| 85 | builtin.TypeId.Union => |info| blk: { | |
| 86 | if (info.tag_type) |tag_type| { | |
| 87 | const tag = meta.activeTag(key); | |
| 88 | const s = autoHash(hasher, tag); | |
| 89 | inline for (info.fields) |field| { | |
| 90 | const enum_field = field.enum_field.?; | |
| 91 | if (enum_field.value == @enumToInt(tag)) { | |
| 92 | autoHash(hasher, @field(key, enum_field.name)); | |
| 93 | // TODO use a labelled break when it does not crash the compiler. | |
| 94 | // break :blk; | |
| 95 | return; | |
| 96 | } | |
| 97 | } | |
| 98 | unreachable; | |
| 99 | } else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function"); | |
| 100 | }, | |
| 101 | ||
| 102 | builtin.TypeId.ErrorUnion => blk: { | |
| 103 | const payload = key catch |err| { | |
| 104 | autoHash(hasher, err); | |
| 105 | break :blk; | |
| 106 | }; | |
| 107 | autoHash(hasher, payload); | |
| 108 | }, | |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | const testing = std.testing; | |
| 113 | const Wyhash = std.hash.Wyhash; | |
| 114 | ||
| 115 | fn testAutoHash(key: var) u64 { | |
| 116 | // Any hash could be used here, for testing autoHash. | |
| 117 | var hasher = Wyhash.init(0); | |
| 118 | autoHash(&hasher, key); | |
| 119 | return hasher.final(); | |
| 120 | } | |
| 121 | ||
| 122 | test "autoHash slice" { | |
| 123 | // Allocate one array dynamically so that we're assured it is not merged | |
| 124 | // with the other by the optimization passes. | |
| 125 | const array1 = try std.heap.direct_allocator.create([6]u32); | |
| 126 | defer std.heap.direct_allocator.destroy(array1); | |
| 127 | array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 }; | |
| 128 | const array2 = [_]u32{ 1, 2, 3, 4, 5, 6 }; | |
| 129 | const a = array1[0..]; | |
| 130 | const b = array2[0..]; | |
| 131 | const c = array1[0..3]; | |
| 132 | testing.expect(testAutoHash(a) == testAutoHash(a)); | |
| 133 | testing.expect(testAutoHash(a) != testAutoHash(array1)); | |
| 134 | testing.expect(testAutoHash(a) != testAutoHash(b)); | |
| 135 | testing.expect(testAutoHash(a) != testAutoHash(c)); | |
| 136 | } | |
| 137 | ||
| 138 | test "testAutoHash optional" { | |
| 139 | const a: ?u32 = 123; | |
| 140 | const b: ?u32 = null; | |
| 141 | testing.expectEqual(testAutoHash(a), testAutoHash(u32(123))); | |
| 142 | testing.expect(testAutoHash(a) != testAutoHash(b)); | |
| 143 | testing.expectEqual(testAutoHash(b), 0); | |
| 144 | } | |
| 145 | ||
| 146 | test "testAutoHash array" { | |
| 147 | const a = [_]u32{ 1, 2, 3 }; | |
| 148 | const h = testAutoHash(a); | |
| 149 | var hasher = Wyhash.init(0); | |
| 150 | autoHash(&hasher, u32(1)); | |
| 151 | autoHash(&hasher, u32(2)); | |
| 152 | autoHash(&hasher, u32(3)); | |
| 153 | testing.expectEqual(h, hasher.final()); | |
| 154 | } | |
| 155 | ||
| 156 | test "testAutoHash struct" { | |
| 157 | const Foo = struct { | |
| 158 | a: u32 = 1, | |
| 159 | b: u32 = 2, | |
| 160 | c: u32 = 3, | |
| 161 | }; | |
| 162 | const f = Foo{}; | |
| 163 | const h = testAutoHash(f); | |
| 164 | var hasher = Wyhash.init(0); | |
| 165 | autoHash(&hasher, u32(1)); | |
| 166 | autoHash(&hasher, u32(2)); | |
| 167 | autoHash(&hasher, u32(3)); | |
| 168 | testing.expectEqual(h, hasher.final()); | |
| 169 | } | |
| 170 | ||
| 171 | test "testAutoHash union" { | |
| 172 | const Foo = union(enum) { | |
| 173 | A: u32, | |
| 174 | B: f32, | |
| 175 | C: u32, | |
| 176 | }; | |
| 177 | ||
| 178 | const a = Foo{ .A = 18 }; | |
| 179 | var b = Foo{ .B = 12.34 }; | |
| 180 | const c = Foo{ .C = 18 }; | |
| 181 | testing.expect(testAutoHash(a) == testAutoHash(a)); | |
| 182 | testing.expect(testAutoHash(a) != testAutoHash(b)); | |
| 183 | testing.expect(testAutoHash(a) != testAutoHash(c)); | |
| 184 | ||
| 185 | b = Foo{ .A = 18 }; | |
| 186 | testing.expect(testAutoHash(a) == testAutoHash(b)); | |
| 187 | } | |
| 188 | ||
| 189 | test "testAutoHash vector" { | |
| 190 | const a: @Vector(4, u32) = [_]u32{ 1, 2, 3, 4 }; | |
| 191 | const b: @Vector(4, u32) = [_]u32{ 1, 2, 3, 5 }; | |
| 192 | const c: @Vector(4, u31) = [_]u31{ 1, 2, 3, 4 }; | |
| 193 | testing.expect(testAutoHash(a) == testAutoHash(a)); | |
| 194 | testing.expect(testAutoHash(a) != testAutoHash(b)); | |
| 195 | testing.expect(testAutoHash(a) != testAutoHash(c)); | |
| 196 | } | |
| 197 | ||
| 198 | test "testAutoHash error union" { | |
| 199 | const Errors = error{Test}; | |
| 200 | const Foo = struct { | |
| 201 | a: u32 = 1, | |
| 202 | b: u32 = 2, | |
| 203 | c: u32 = 3, | |
| 204 | }; | |
| 205 | const f = Foo{}; | |
| 206 | const g: Errors!Foo = Errors.Test; | |
| 207 | testing.expect(testAutoHash(f) != testAutoHash(g)); | |
| 208 | testing.expect(testAutoHash(f) == testAutoHash(Foo{})); | |
| 209 | testing.expect(testAutoHash(g) == testAutoHash(Errors.Test)); | |
| 210 | } |
std/hash/throughput_test.zig created+148| ... | ... | @@ -0,0 +1,148 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("std"); | |
| 3 | const time = std.time; | |
| 4 | const Timer = time.Timer; | |
| 5 | const hash = std.hash; | |
| 6 | ||
| 7 | const KiB = 1024; | |
| 8 | const MiB = 1024 * KiB; | |
| 9 | const GiB = 1024 * MiB; | |
| 10 | ||
| 11 | var prng = std.rand.DefaultPrng.init(0); | |
| 12 | ||
| 13 | const Hash = struct { | |
| 14 | ty: type, | |
| 15 | name: []const u8, | |
| 16 | init_u8s: ?[]const u8 = null, | |
| 17 | init_u64: ?u64 = null, | |
| 18 | }; | |
| 19 | ||
| 20 | const siphash_key = "0123456789abcdef"; | |
| 21 | ||
| 22 | const hashes = [_]Hash{ | |
| 23 | Hash{ .ty = hash.Wyhash, .name = "wyhash", .init_u64 = 0 }, | |
| 24 | Hash{ .ty = hash.SipHash64(1, 3), .name = "siphash(1,3)", .init_u8s = siphash_key }, | |
| 25 | Hash{ .ty = hash.SipHash64(2, 4), .name = "siphash(2,4)", .init_u8s = siphash_key }, | |
| 26 | Hash{ .ty = hash.Fnv1a_64, .name = "fnv1a" }, | |
| 27 | Hash{ .ty = hash.Crc32, .name = "crc32" }, | |
| 28 | }; | |
| 29 | ||
| 30 | const Result = struct { | |
| 31 | hash: u64, | |
| 32 | throughput: u64, | |
| 33 | }; | |
| 34 | ||
| 35 | pub fn benchmarkHash(comptime H: var, bytes: usize) !Result { | |
| 36 | var h = blk: { | |
| 37 | if (H.init_u8s) |init| { | |
| 38 | break :blk H.ty.init(init); | |
| 39 | } | |
| 40 | if (H.init_u64) |init| { | |
| 41 | break :blk H.ty.init(init); | |
| 42 | } | |
| 43 | break :blk H.ty.init(); | |
| 44 | }; | |
| 45 | ||
| 46 | var block: [8192]u8 = undefined; | |
| 47 | prng.random.bytes(block[0..]); | |
| 48 | ||
| 49 | var offset: usize = 0; | |
| 50 | var timer = try Timer.start(); | |
| 51 | const start = timer.lap(); | |
| 52 | while (offset < bytes) : (offset += block.len) { | |
| 53 | h.update(block[0..]); | |
| 54 | } | |
| 55 | const end = timer.read(); | |
| 56 | ||
| 57 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; | |
| 58 | const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s); | |
| 59 | ||
| 60 | return Result{ | |
| 61 | .hash = h.final(), | |
| 62 | .throughput = throughput, | |
| 63 | }; | |
| 64 | } | |
| 65 | ||
| 66 | fn usage() void { | |
| 67 | std.debug.warn( | |
| 68 | \\throughput_test [options] | |
| 69 | \\ | |
| 70 | \\Options: | |
| 71 | \\ --filter [test-name] | |
| 72 | \\ --seed [int] | |
| 73 | \\ --count [int] | |
| 74 | \\ --help | |
| 75 | \\ | |
| 76 | ); | |
| 77 | } | |
| 78 | ||
| 79 | fn mode(comptime x: comptime_int) comptime_int { | |
| 80 | return if (builtin.mode == builtin.Mode.Debug) x / 64 else x; | |
| 81 | } | |
| 82 | ||
| 83 | // TODO(#1358): Replace with builtin formatted padding when available. | |
| 84 | fn printPad(stdout: var, s: []const u8) !void { | |
| 85 | var i: usize = 0; | |
| 86 | while (i < 12 - s.len) : (i += 1) { | |
| 87 | try stdout.print(" "); | |
| 88 | } | |
| 89 | try stdout.print("{}", s); | |
| 90 | } | |
| 91 | ||
| 92 | pub fn main() !void { | |
| 93 | var stdout_file = try std.io.getStdOut(); | |
| 94 | var stdout_out_stream = stdout_file.outStream(); | |
| 95 | const stdout = &stdout_out_stream.stream; | |
| 96 | ||
| 97 | var buffer: [1024]u8 = undefined; | |
| 98 | var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); | |
| 99 | const args = try std.process.argsAlloc(&fixed.allocator); | |
| 100 | ||
| 101 | var filter: ?[]u8 = ""; | |
| 102 | var count: usize = mode(128 * MiB); | |
| 103 | ||
| 104 | var i: usize = 1; | |
| 105 | while (i < args.len) : (i += 1) { | |
| 106 | if (std.mem.eql(u8, args[i], "--seed")) { | |
| 107 | i += 1; | |
| 108 | if (i == args.len) { | |
| 109 | usage(); | |
| 110 | std.os.exit(1); | |
| 111 | } | |
| 112 | ||
| 113 | const seed = try std.fmt.parseUnsigned(u32, args[i], 10); | |
| 114 | prng.seed(seed); | |
| 115 | } else if (std.mem.eql(u8, args[i], "--filter")) { | |
| 116 | i += 1; | |
| 117 | if (i == args.len) { | |
| 118 | usage(); | |
| 119 | std.os.exit(1); | |
| 120 | } | |
| 121 | ||
| 122 | filter = args[i]; | |
| 123 | } else if (std.mem.eql(u8, args[i], "--count")) { | |
| 124 | i += 1; | |
| 125 | if (i == args.len) { | |
| 126 | usage(); | |
| 127 | std.os.exit(1); | |
| 128 | } | |
| 129 | ||
| 130 | const c = try std.fmt.parseUnsigned(u32, args[i], 10); | |
| 131 | count = c * MiB; | |
| 132 | } else if (std.mem.eql(u8, args[i], "--help")) { | |
| 133 | usage(); | |
| 134 | return; | |
| 135 | } else { | |
| 136 | usage(); | |
| 137 | std.os.exit(1); | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | inline for (hashes) |H| { | |
| 142 | if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) { | |
| 143 | const result = try benchmarkHash(H, count); | |
| 144 | try printPad(stdout, H.name); | |
| 145 | try stdout.print(": {:4} MiB/s [{:16}]\n", result.throughput / (1 * MiB), result.hash); | |
| 146 | } | |
| 147 | } | |
| 148 | } |
std/hash/wyhash.zig created+135| ... | ... | @@ -0,0 +1,135 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | ||
| 4 | const primes = [_]u64{ | |
| 5 | 0xa0761d6478bd642f, | |
| 6 | 0xe7037ed1a0b428db, | |
| 7 | 0x8ebc6af09c88c6e3, | |
| 8 | 0x589965cc75374cc3, | |
| 9 | 0x1d8e4e27c47d124f, | |
| 10 | }; | |
| 11 | ||
| 12 | fn read_bytes(comptime bytes: u8, data: []const u8) u64 { | |
| 13 | return mem.readVarInt(u64, data[0..bytes], .Little); | |
| 14 | } | |
| 15 | ||
| 16 | fn read_8bytes_swapped(data: []const u8) u64 { | |
| 17 | return (read_bytes(4, data) << 32 | read_bytes(4, data[4..])); | |
| 18 | } | |
| 19 | ||
| 20 | fn mum(a: u64, b: u64) u64 { | |
| 21 | var r = std.math.mulWide(u64, a, b); | |
| 22 | r = (r >> 64) ^ r; | |
| 23 | return @truncate(u64, r); | |
| 24 | } | |
| 25 | ||
| 26 | fn mix0(a: u64, b: u64, seed: u64) u64 { | |
| 27 | return mum(a ^ seed ^ primes[0], b ^ seed ^ primes[1]); | |
| 28 | } | |
| 29 | ||
| 30 | fn mix1(a: u64, b: u64, seed: u64) u64 { | |
| 31 | return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]); | |
| 32 | } | |
| 33 | ||
| 34 | pub const Wyhash = struct { | |
| 35 | seed: u64, | |
| 36 | msg_len: usize, | |
| 37 | ||
| 38 | pub fn init(seed: u64) Wyhash { | |
| 39 | return Wyhash{ | |
| 40 | .seed = seed, | |
| 41 | .msg_len = 0, | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | fn round(self: *Wyhash, b: []const u8) void { | |
| 46 | std.debug.assert(b.len == 32); | |
| 47 | ||
| 48 | self.seed = mix0( | |
| 49 | read_bytes(8, b[0..]), | |
| 50 | read_bytes(8, b[8..]), | |
| 51 | self.seed, | |
| 52 | ) ^ mix1( | |
| 53 | read_bytes(8, b[16..]), | |
| 54 | read_bytes(8, b[24..]), | |
| 55 | self.seed, | |
| 56 | ); | |
| 57 | } | |
| 58 | ||
| 59 | fn partial(self: *Wyhash, b: []const u8) void { | |
| 60 | const rem_key = b; | |
| 61 | const rem_len = b.len; | |
| 62 | ||
| 63 | var seed = self.seed; | |
| 64 | seed = switch (@intCast(u5, rem_len)) { | |
| 65 | 0 => seed, | |
| 66 | 1 => mix0(read_bytes(1, rem_key), primes[4], seed), | |
| 67 | 2 => mix0(read_bytes(2, rem_key), primes[4], seed), | |
| 68 | 3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed), | |
| 69 | 4 => mix0(read_bytes(4, rem_key), primes[4], seed), | |
| 70 | 5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed), | |
| 71 | 6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed), | |
| 72 | 7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed), | |
| 73 | 8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed), | |
| 74 | 9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed), | |
| 75 | 10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed), | |
| 76 | 11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed), | |
| 77 | 12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed), | |
| 78 | 13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed), | |
| 79 | 14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed), | |
| 80 | 15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed), | |
| 81 | 16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed), | |
| 82 | 17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed), | |
| 83 | 18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed), | |
| 84 | 19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed), | |
| 85 | 20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed), | |
| 86 | 21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed), | |
| 87 | 22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed), | |
| 88 | 23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed), | |
| 89 | 24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed), | |
| 90 | 25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed), | |
| 91 | 26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed), | |
| 92 | 27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed), | |
| 93 | 28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed), | |
| 94 | 29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed), | |
| 95 | 30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed), | |
| 96 | 31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed), | |
| 97 | }; | |
| 98 | self.seed = seed; | |
| 99 | } | |
| 100 | ||
| 101 | pub fn update(self: *Wyhash, b: []const u8) void { | |
| 102 | var off: usize = 0; | |
| 103 | ||
| 104 | // Full middle blocks. | |
| 105 | while (off + 32 <= b.len) : (off += 32) { | |
| 106 | @inlineCall(self.round, b[off .. off + 32]); | |
| 107 | } | |
| 108 | ||
| 109 | self.partial(b[off..]); | |
| 110 | self.msg_len += b.len; | |
| 111 | } | |
| 112 | ||
| 113 | pub fn final(self: *Wyhash) u64 { | |
| 114 | return mum(self.seed ^ self.msg_len, primes[4]); | |
| 115 | } | |
| 116 | ||
| 117 | pub fn hash(seed: u64, input: []const u8) u64 { | |
| 118 | var c = Wyhash.init(seed); | |
| 119 | c.update(input); | |
| 120 | return c.final(); | |
| 121 | } | |
| 122 | }; | |
| 123 | ||
| 124 | test "test vectors" { | |
| 125 | const expectEqual = std.testing.expectEqual; | |
| 126 | const hash = Wyhash.hash; | |
| 127 | ||
| 128 | expectEqual(hash(0, ""), 0x0); | |
| 129 | expectEqual(hash(1, "a"), 0xbed235177f41d328); | |
| 130 | expectEqual(hash(2, "abc"), 0xbe348debe59b27c3); | |
| 131 | expectEqual(hash(3, "message digest"), 0x37320f657213a290); | |
| 132 | expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c); | |
| 133 | expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f); | |
| 134 | expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e); | |
| 135 | } |
std/hash_map.zig+11-112| ... | ... | @@ -4,6 +4,9 @@ const assert = debug.assert; |
| 4 | 4 | const testing = std.testing; |
| 5 | 5 | const math = std.math; |
| 6 | 6 | const mem = std.mem; |
| 7 | const meta = std.meta; | |
| 8 | const autoHash = std.hash.autoHash; | |
| 9 | const Wyhash = std.hash.Wyhash; | |
| 7 | 10 | const Allocator = mem.Allocator; |
| 8 | 11 | const builtin = @import("builtin"); |
| 9 | 12 | |
| ... | ... | @@ -448,15 +451,17 @@ test "iterator hash map" { |
| 448 | 451 | try reset_map.putNoClobber(2, 22); |
| 449 | 452 | try reset_map.putNoClobber(3, 33); |
| 450 | 453 | |
| 454 | // TODO this test depends on the hashing algorithm, because it assumes the | |
| 455 | // order of the elements in the hashmap. This should not be the case. | |
| 451 | 456 | var keys = [_]i32{ |
| 457 | 1, | |
| 452 | 458 | 3, |
| 453 | 459 | 2, |
| 454 | 1, | |
| 455 | 460 | }; |
| 456 | 461 | var values = [_]i32{ |
| 462 | 11, | |
| 457 | 463 | 33, |
| 458 | 464 | 22, |
| 459 | 11, | |
| 460 | 465 | }; |
| 461 | 466 | |
| 462 | 467 | var it = reset_map.iterator(); |
| ... | ... | @@ -518,8 +523,9 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { |
| 518 | 523 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 519 | 524 | return struct { |
| 520 | 525 | fn hash(key: K) u32 { |
| 521 | comptime var rng = comptime std.rand.DefaultPrng.init(0); | |
| 522 | return autoHash(key, &rng.random, u32); | |
| 526 | var hasher = Wyhash.init(0); | |
| 527 | autoHash(&hasher, key); | |
| 528 | return @truncate(u32, hasher.final()); | |
| 523 | 529 | } |
| 524 | 530 | }.hash; |
| 525 | 531 | } |
| ... | ... | @@ -527,114 +533,7 @@ pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 527 | 533 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { |
| 528 | 534 | return struct { |
| 529 | 535 | fn eql(a: K, b: K) bool { |
| 530 | return autoEql(a, b); | |
| 536 | return meta.eql(a, b); | |
| 531 | 537 | } |
| 532 | 538 | }.eql; |
| 533 | 539 | } |
| 534 | ||
| 535 | // TODO improve these hash functions | |
| 536 | pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt { | |
| 537 | switch (@typeInfo(@typeOf(key))) { | |
| 538 | builtin.TypeId.NoReturn, | |
| 539 | builtin.TypeId.Opaque, | |
| 540 | builtin.TypeId.Undefined, | |
| 541 | builtin.TypeId.ArgTuple, | |
| 542 | => @compileError("cannot hash this type"), | |
| 543 | ||
| 544 | builtin.TypeId.Void, | |
| 545 | builtin.TypeId.Null, | |
| 546 | => return 0, | |
| 547 | ||
| 548 | builtin.TypeId.Int => |info| { | |
| 549 | const unsigned_x = @bitCast(@IntType(false, info.bits), key); | |
| 550 | if (info.bits <= HashInt.bit_count) { | |
| 551 | return HashInt(unsigned_x) ^ comptime rng.scalar(HashInt); | |
| 552 | } else { | |
| 553 | return @truncate(HashInt, unsigned_x ^ comptime rng.scalar(@typeOf(unsigned_x))); | |
| 554 | } | |
| 555 | }, | |
| 556 | ||
| 557 | builtin.TypeId.Float => |info| { | |
| 558 | return autoHash(@bitCast(@IntType(false, info.bits), key), rng, HashInt); | |
| 559 | }, | |
| 560 | builtin.TypeId.Bool => return autoHash(@boolToInt(key), rng, HashInt), | |
| 561 | builtin.TypeId.Enum => return autoHash(@enumToInt(key), rng, HashInt), | |
| 562 | builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), rng, HashInt), | |
| 563 | builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), rng, HashInt), | |
| 564 | ||
| 565 | builtin.TypeId.BoundFn, | |
| 566 | builtin.TypeId.ComptimeFloat, | |
| 567 | builtin.TypeId.ComptimeInt, | |
| 568 | builtin.TypeId.Type, | |
| 569 | builtin.TypeId.EnumLiteral, | |
| 570 | => return 0, | |
| 571 | ||
| 572 | builtin.TypeId.Pointer => |info| switch (info.size) { | |
| 573 | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"), | |
| 574 | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"), | |
| 575 | builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto hash C pointers"), | |
| 576 | builtin.TypeInfo.Pointer.Size.Slice => { | |
| 577 | const interval = std.math.max(1, key.len / 256); | |
| 578 | var i: usize = 0; | |
| 579 | var h = comptime rng.scalar(HashInt); | |
| 580 | while (i < key.len) : (i += interval) { | |
| 581 | h ^= autoHash(key[i], rng, HashInt); | |
| 582 | } | |
| 583 | return h; | |
| 584 | }, | |
| 585 | }, | |
| 586 | ||
| 587 | builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"), | |
| 588 | builtin.TypeId.Array => @compileError("TODO auto hash for arrays"), | |
| 589 | builtin.TypeId.Vector => @compileError("TODO auto hash for vectors"), | |
| 590 | builtin.TypeId.Struct => @compileError("TODO auto hash for structs"), | |
| 591 | builtin.TypeId.Union => @compileError("TODO auto hash for unions"), | |
| 592 | builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"), | |
| 593 | } | |
| 594 | } | |
| 595 | ||
| 596 | pub fn autoEql(a: var, b: @typeOf(a)) bool { | |
| 597 | switch (@typeInfo(@typeOf(a))) { | |
| 598 | builtin.TypeId.NoReturn, | |
| 599 | builtin.TypeId.Opaque, | |
| 600 | builtin.TypeId.Undefined, | |
| 601 | builtin.TypeId.ArgTuple, | |
| 602 | => @compileError("cannot test equality of this type"), | |
| 603 | builtin.TypeId.Void, | |
| 604 | builtin.TypeId.Null, | |
| 605 | => return true, | |
| 606 | builtin.TypeId.Bool, | |
| 607 | builtin.TypeId.Int, | |
| 608 | builtin.TypeId.Float, | |
| 609 | builtin.TypeId.ComptimeFloat, | |
| 610 | builtin.TypeId.ComptimeInt, | |
| 611 | builtin.TypeId.EnumLiteral, | |
| 612 | builtin.TypeId.Promise, | |
| 613 | builtin.TypeId.Enum, | |
| 614 | builtin.TypeId.BoundFn, | |
| 615 | builtin.TypeId.Fn, | |
| 616 | builtin.TypeId.ErrorSet, | |
| 617 | builtin.TypeId.Type, | |
| 618 | => return a == b, | |
| 619 | ||
| 620 | builtin.TypeId.Pointer => |info| switch (info.size) { | |
| 621 | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"), | |
| 622 | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"), | |
| 623 | builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto eql for C pointers"), | |
| 624 | builtin.TypeInfo.Pointer.Size.Slice => { | |
| 625 | if (a.len != b.len) return false; | |
| 626 | for (a) |a_item, i| { | |
| 627 | if (!autoEql(a_item, b[i])) return false; | |
| 628 | } | |
| 629 | return true; | |
| 630 | }, | |
| 631 | }, | |
| 632 | ||
| 633 | builtin.TypeId.Optional => @compileError("TODO auto eql for optionals"), | |
| 634 | builtin.TypeId.Array => @compileError("TODO auto eql for arrays"), | |
| 635 | builtin.TypeId.Struct => @compileError("TODO auto eql for structs"), | |
| 636 | builtin.TypeId.Union => @compileError("TODO auto eql for unions"), | |
| 637 | builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"), | |
| 638 | builtin.TypeId.Vector => @compileError("TODO auto eql for vectors"), | |
| 639 | } | |
| 640 | } |
std/http/headers.zig+11-1| ... | ... | @@ -102,9 +102,19 @@ test "HeaderEntry" { |
| 102 | 102 | testing.expectEqualSlices(u8, "x", e.value); |
| 103 | 103 | } |
| 104 | 104 | |
| 105 | fn stringEql(a: []const u8, b: []const u8) bool { | |
| 106 | if (a.len != b.len) return false; | |
| 107 | if (a.ptr == b.ptr) return true; | |
| 108 | return mem.compare(u8, a, b) == .Equal; | |
| 109 | } | |
| 110 | ||
| 111 | fn stringHash(s: []const u8) u32 { | |
| 112 | return @truncate(u32, std.hash.Wyhash.hash(0, s)); | |
| 113 | } | |
| 114 | ||
| 105 | 115 | const HeaderList = std.ArrayList(HeaderEntry); |
| 106 | 116 | const HeaderIndexList = std.ArrayList(usize); |
| 107 | const HeaderIndex = std.AutoHashMap([]const u8, HeaderIndexList); | |
| 117 | const HeaderIndex = std.HashMap([]const u8, HeaderIndexList, stringHash, stringEql); | |
| 108 | 118 | |
| 109 | 119 | pub const Headers = struct { |
| 110 | 120 | // the owned header field name is stored in the index as part of the key |