| ... | @@ -10,7 +10,8 @@ const primes = [_]u64{ | ... | @@ -10,7 +10,8 @@ const primes = [_]u64{ |
| 10 | }; | 10 | }; |
| 11 | | 11 | |
| 12 | fn read_bytes(comptime bytes: u8, data: []const u8) u64 { | 12 | fn read_bytes(comptime bytes: u8, data: []const u8) u64 { |
| 13 | return mem.readVarInt(u64, data[0..bytes], .Little); | 13 | const T = @IntType(false, 8 * bytes); |
| | 14 | return mem.readIntSliceLittle(T, data[0..bytes]); |
| 14 | } | 15 | } |
| 15 | | 16 | |
| 16 | fn read_8bytes_swapped(data: []const u8) u64 { | 17 | fn read_8bytes_swapped(data: []const u8) u64 { |
| ... | @@ -31,25 +32,21 @@ fn mix1(a: u64, b: u64, seed: u64) u64 { | ... | @@ -31,25 +32,21 @@ fn mix1(a: u64, b: u64, seed: u64) u64 { |
| 31 | return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]); | 32 | return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]); |
| 32 | } | 33 | } |
| 33 | | 34 | |
| 34 | /// Fast non-cryptographic 64bit hash function. | 35 | // Wyhash version which does not store internal state for handling partial buffers. |
| 35 | /// See https://github.com/wangyi-fudan/wyhash | 36 | // This is needed so that we can maximize the speed for the short key case, which will |
| 36 | pub const Wyhash = struct { | 37 | // use the non-iterative api which the public Wyhash exposes. |
| | 38 | const WyhashStateless = struct { |
| 37 | seed: u64, | 39 | seed: u64, |
| 38 | | | |
| 39 | buf: [32]u8, | | |
| 40 | buf_len: usize, | | |
| 41 | msg_len: usize, | 40 | msg_len: usize, |
| 42 | | 41 | |
| 43 | pub fn init(seed: u64) Wyhash { | 42 | pub fn init(seed: u64) WyhashStateless { |
| 44 | return Wyhash{ | 43 | return WyhashStateless{ |
| 45 | .seed = seed, | 44 | .seed = seed, |
| 46 | .buf = undefined, | | |
| 47 | .buf_len = 0, | | |
| 48 | .msg_len = 0, | 45 | .msg_len = 0, |
| 49 | }; | 46 | }; |
| 50 | } | 47 | } |
| 51 | | 48 | |
| 52 | fn round(self: *Wyhash, b: []const u8) void { | 49 | fn round(self: *WyhashStateless, b: []const u8) void { |
| 53 | std.debug.assert(b.len == 32); | 50 | std.debug.assert(b.len == 32); |
| 54 | | 51 | |
| 55 | self.seed = mix0( | 52 | self.seed = mix0( |
| ... | @@ -63,32 +60,23 @@ pub const Wyhash = struct { | ... | @@ -63,32 +60,23 @@ pub const Wyhash = struct { |
| 63 | ); | 60 | ); |
| 64 | } | 61 | } |
| 65 | | 62 | |
| 66 | pub fn update(self: *Wyhash, b: []const u8) void { | 63 | pub fn update(self: *WyhashStateless, b: []const u8) void { |
| 67 | var off: usize = 0; | 64 | std.debug.assert(b.len % 32 == 0); |
| 68 | | | |
| 69 | // Partial from previous. | | |
| 70 | if (self.buf_len != 0 and self.buf_len + b.len > 32) { | | |
| 71 | off += 32 - self.buf_len; | | |
| 72 | mem.copy(u8, self.buf[self.buf_len..], b[0..off]); | | |
| 73 | self.round(self.buf[0..]); | | |
| 74 | self.buf_len = 0; | | |
| 75 | } | | |
| 76 | | 65 | |
| 77 | // Full middle blocks. | 66 | var off: usize = 0; |
| 78 | while (off + 32 <= b.len) : (off += 32) { | 67 | while (off < b.len) : (off += 32) { |
| 79 | @inlineCall(self.round, b[off .. off + 32]); | 68 | @inlineCall(self.round, b[off .. off + 32]); |
| 80 | } | 69 | } |
| 81 | | 70 | |
| 82 | // Remainder for next pass. | | |
| 83 | mem.copy(u8, self.buf[self.buf_len..], b[off..]); | | |
| 84 | self.buf_len += @intCast(u8, b[off..].len); | | |
| 85 | self.msg_len += b.len; | 71 | self.msg_len += b.len; |
| 86 | } | 72 | } |
| 87 | | 73 | |
| 88 | pub fn final(self: *Wyhash) u64 { | 74 | pub fn final(self: *WyhashStateless, b: []const u8) u64 { |
| | 75 | std.debug.assert(b.len < 32); |
| | 76 | |
| 89 | const seed = self.seed; | 77 | const seed = self.seed; |
| 90 | const rem_len = @intCast(u5, self.buf_len); | 78 | const rem_len = @intCast(u5, b.len); |
| 91 | const rem_key = self.buf[0..self.buf_len]; | 79 | const rem_key = b[0..rem_len]; |
| 92 | | 80 | |
| 93 | self.seed = switch (rem_len) { | 81 | self.seed = switch (rem_len) { |
| 94 | 0 => seed, | 82 | 0 => seed, |
| ... | @@ -125,109 +113,63 @@ pub const Wyhash = struct { | ... | @@ -125,109 +113,63 @@ pub const Wyhash = struct { |
| 125 | 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), | 113 | 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), |
| 126 | }; | 114 | }; |
| 127 | | 115 | |
| | 116 | self.msg_len += b.len; |
| 128 | return mum(self.seed ^ self.msg_len, primes[4]); | 117 | return mum(self.seed ^ self.msg_len, primes[4]); |
| 129 | } | 118 | } |
| 130 | | 119 | |
| 131 | pub fn hash(seed: u64, input: []const u8) u64 { | 120 | pub fn hash(seed: u64, input: []const u8) u64 { |
| 132 | var c = Wyhash.init(seed); | 121 | const aligned_len = input.len - (input.len % 32); |
| 133 | @inlineCall(c.update, input); | | |
| 134 | return @inlineCall(c.final); | | |
| 135 | } | | |
| 136 | }; | | |
| 137 | | | |
| 138 | /// Wyhash version where state is not preserved between successive `update` | | |
| 139 | /// calls, ie. it will have different results between hashing the data in | | |
| 140 | /// one or several steps. | | |
| 141 | /// This allows it to be faster. | | |
| 142 | pub const WyhashStateless = struct { | | |
| 143 | seed: u64, | | |
| 144 | msg_len: usize, | | |
| 145 | | 122 | |
| 146 | const Self = @This(); | 123 | var c = WyhashStateless.init(seed); |
| 147 | | 124 | @inlineCall(c.update, input[0..aligned_len]); |
| 148 | pub fn init(seed: u64) Self { | 125 | return @inlineCall(c.final, input[aligned_len..]); |
| 149 | return Self{ | | |
| 150 | .seed = seed, | | |
| 151 | .msg_len = 0, | | |
| 152 | }; | | |
| 153 | } | 126 | } |
| | 127 | }; |
| 154 | | 128 | |
| 155 | fn round(self: *Self, b: []const u8) void { | 129 | /// Fast non-cryptographic 64bit hash function. |
| 156 | std.debug.assert(b.len == 32); | 130 | /// See https://github.com/wangyi-fudan/wyhash |
| 157 | | 131 | pub const Wyhash = struct { |
| 158 | self.seed = mix0( | 132 | state: WyhashStateless, |
| 159 | read_bytes(8, b[0..]), | | |
| 160 | read_bytes(8, b[8..]), | | |
| 161 | self.seed, | | |
| 162 | ) ^ mix1( | | |
| 163 | read_bytes(8, b[16..]), | | |
| 164 | read_bytes(8, b[24..]), | | |
| 165 | self.seed, | | |
| 166 | ); | | |
| 167 | } | | |
| 168 | | 133 | |
| 169 | fn partial(self: *Self, b: []const u8) void { | 134 | buf: [32]u8, |
| 170 | const rem_key = b; | 135 | buf_len: usize, |
| 171 | const rem_len = b.len; | | |
| 172 | | 136 | |
| 173 | var seed = self.seed; | 137 | pub fn init(seed: u64) Wyhash { |
| 174 | seed = switch (@intCast(u5, rem_len)) { | 138 | return Wyhash{ |
| 175 | 0 => seed, | 139 | .state = WyhashStateless.init(seed), |
| 176 | 1 => mix0(read_bytes(1, rem_key), primes[4], seed), | 140 | .buf = undefined, |
| 177 | 2 => mix0(read_bytes(2, rem_key), primes[4], seed), | 141 | .buf_len = 0, |
| 178 | 3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed), | | |
| 179 | 4 => mix0(read_bytes(4, rem_key), primes[4], seed), | | |
| 180 | 5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed), | | |
| 181 | 6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed), | | |
| 182 | 7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed), | | |
| 183 | 8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed), | | |
| 184 | 9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed), | | |
| 185 | 10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed), | | |
| 186 | 11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed), | | |
| 187 | 12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed), | | |
| 188 | 13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed), | | |
| 189 | 14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed), | | |
| 190 | 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), | | |
| 191 | 16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed), | | |
| 192 | 17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed), | | |
| 193 | 18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed), | | |
| 194 | 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), | | |
| 195 | 20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed), | | |
| 196 | 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), | | |
| 197 | 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), | | |
| 198 | 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), | | |
| 199 | 24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed), | | |
| 200 | 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), | | |
| 201 | 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), | | |
| 202 | 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), | | |
| 203 | 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), | | |
| 204 | 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), | | |
| 205 | 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), | | |
| 206 | 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), | | |
| 207 | }; | 142 | }; |
| 208 | self.seed = seed; | | |
| 209 | } | 143 | } |
| 210 | | 144 | |
| 211 | pub fn update(self: *Self, b: []const u8) void { | 145 | pub fn update(self: *Wyhash, b: []const u8) void { |
| 212 | var off: usize = 0; | 146 | var off: usize = 0; |
| 213 | | 147 | |
| 214 | // Full middle blocks. | 148 | if (self.buf_len != 0 and self.buf_len + b.len >= 32) { |
| 215 | while (off + 32 <= b.len) : (off += 32) { | 149 | off += 32 - self.buf_len; |
| 216 | @inlineCall(self.round, b[off .. off + 32]); | 150 | mem.copy(u8, self.buf[self.buf_len..], b[0..off]); |
| | 151 | self.state.update(self.buf[0..]); |
| | 152 | self.buf_len = 0; |
| 217 | } | 153 | } |
| 218 | | 154 | |
| 219 | self.partial(b[off..]); | 155 | const remain_len = b.len - off; |
| 220 | self.msg_len += b.len; | 156 | const aligned_len = remain_len - (remain_len % 32); |
| | 157 | self.state.update(b[off .. off + aligned_len]); |
| | 158 | |
| | 159 | mem.copy(u8, self.buf[self.buf_len..], b[off + aligned_len ..]); |
| | 160 | self.buf_len += @intCast(u8, b[off + aligned_len ..].len); |
| 221 | } | 161 | } |
| 222 | | 162 | |
| 223 | pub fn final(self: *Self) u64 { | 163 | pub fn final(self: *Wyhash) u64 { |
| 224 | return mum(self.seed ^ self.msg_len, primes[4]); | 164 | const seed = self.state.seed; |
| | 165 | const rem_len = @intCast(u5, self.buf_len); |
| | 166 | const rem_key = self.buf[0..self.buf_len]; |
| | 167 | |
| | 168 | return self.state.final(rem_key); |
| 225 | } | 169 | } |
| 226 | | 170 | |
| 227 | pub fn hash(seed: u64, input: []const u8) u64 { | 171 | pub fn hash(seed: u64, input: []const u8) u64 { |
| 228 | var c = Self.init(seed); | 172 | return WyhashStateless.hash(seed, input); |
| 229 | @inlineCall(c.update, input); | | |
| 230 | return @inlineCall(c.final); | | |
| 231 | } | 173 | } |
| 232 | }; | 174 | }; |
| 233 | | 175 | |
| ... | @@ -265,17 +207,25 @@ test "test vectors streaming" { | ... | @@ -265,17 +207,25 @@ test "test vectors streaming" { |
| 265 | expectEqual(wh.final(), result); | 207 | expectEqual(wh.final(), result); |
| 266 | } | 208 | } |
| 267 | | 209 | |
| 268 | test "test vectors stateless" { | 210 | test "iterative non-divisible update" { |
| 269 | const hash = WyhashStateless.hash; | 211 | var buf: [8192]u8 = undefined; |
| | 212 | for (buf) |*e, i| { |
| | 213 | e.* = @truncate(u8, i); |
| | 214 | } |
| 270 | | 215 | |
| 271 | expectEqual(hash(0, ""), 0x0); | 216 | const seed = 0x128dad08f; |
| 272 | expectEqual(hash(1, "a"), 0xbed235177f41d328); | 217 | |
| 273 | expectEqual(hash(2, "abc"), 0xbe348debe59b27c3); | 218 | var end: usize = 32; |
| 274 | expectEqual(hash(3, "message digest"), 0x37320f657213a290); | 219 | while (end < buf.len) : (end += 32) { |
| 275 | expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c); | 220 | const non_iterative_hash = Wyhash.hash(seed, buf[0..end]); |
| 276 | expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f); | | |
| 277 | expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e); | | |
| 278 | | 221 | |
| 279 | // We don't check for the streaming API having the same results, as it is | 222 | var wy = Wyhash.init(seed); |
| 280 | // not required to. | 223 | var i: usize = 0; |
| | 224 | while (i < end) : (i += 33) { |
| | 225 | wy.update(buf[i..std.math.min(i + 33, end)]); |
| | 226 | } |
| | 227 | const iterative_hash = wy.final(); |
| | 228 | |
| | 229 | std.testing.expectEqual(iterative_hash, non_iterative_hash); |
| | 230 | } |
| 281 | } | 231 | } |