authorgravatar for chris@bodcompany.comChris Covington <chris@bodcompany.com> 2025-02-22 14:32:48-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-02-22 14:32:48-08:00
logb7512c3e5d1734c301b8e5c1cda0385b0a967e0c
tree76acf9da4580dcc482fedf742cc31711eafd1310
parent33ffbb32c9cfea28373b199003f94fa80353ee44
signaturebadge-check Signed by PGP key B5690EEEBB952194

Add rapidhash (#22085)


3 files changed, 144 insertions(+), 1 deletions(-)

lib/std/hash.zig+2
...@@ -32,6 +32,8 @@ pub const CityHash64 = cityhash.CityHash64;...@@ -32,6 +32,8 @@ pub const CityHash64 = cityhash.CityHash64;
32const wyhash = @import("hash/wyhash.zig");32const wyhash = @import("hash/wyhash.zig");
33pub const Wyhash = wyhash.Wyhash;33pub const Wyhash = wyhash.Wyhash;
3434
35pub const RapidHash = @import("hash/RapidHash.zig");
36
35const xxhash = @import("hash/xxhash.zig");37const xxhash = @import("hash/xxhash.zig");
36pub const XxHash3 = xxhash.XxHash3;38pub const XxHash3 = xxhash.XxHash3;
37pub const XxHash64 = xxhash.XxHash64;39pub const XxHash64 = xxhash.XxHash64;
lib/std/hash/RapidHash.zig created+125
...@@ -0,0 +1,125 @@
1const std = @import("std");
2
3const readInt = std.mem.readInt;
4const assert = std.debug.assert;
5const expect = std.testing.expect;
6const expectEqual = std.testing.expectEqual;
7
8const RAPID_SEED: u64 = 0xbdd89aa982704029;
9const RAPID_SECRET: [3]u64 = .{ 0x2d358dccaa6c78a5, 0x8bb84b93962eacc9, 0x4b33a62ed433d4a3 };
10
11pub fn hash(seed: u64, input: []const u8) u64 {
12 const sc = RAPID_SECRET;
13 const len = input.len;
14 var a: u64 = 0;
15 var b: u64 = 0;
16 var k = input;
17 var is: [3]u64 = .{ seed, 0, 0 };
18
19 is[0] ^= mix(seed ^ sc[0], sc[1]) ^ len;
20
21 if (len <= 16) {
22 if (len >= 4) {
23 const d: u64 = ((len & 24) >> @intCast(len >> 3));
24 const e = len - 4;
25 a = (r32(k) << 32) | r32(k[e..]);
26 b = ((r32(k[d..]) << 32) | r32(k[(e - d)..]));
27 } else if (len > 0)
28 a = (@as(u64, k[0]) << 56) | (@as(u64, k[len >> 1]) << 32) | @as(u64, k[len - 1]);
29 } else {
30 var remain = len;
31 if (len > 48) {
32 is[1] = is[0];
33 is[2] = is[0];
34 while (remain >= 96) {
35 inline for (0..6) |i| {
36 const m1 = r64(k[8 * i * 2 ..]);
37 const m2 = r64(k[8 * (i * 2 + 1) ..]);
38 is[i % 3] = mix(m1 ^ sc[i % 3], m2 ^ is[i % 3]);
39 }
40 k = k[96..];
41 remain -= 96;
42 }
43 if (remain >= 48) {
44 inline for (0..3) |i| {
45 const m1 = r64(k[8 * i * 2 ..]);
46 const m2 = r64(k[8 * (i * 2 + 1) ..]);
47 is[i] = mix(m1 ^ sc[i], m2 ^ is[i]);
48 }
49 k = k[48..];
50 remain -= 48;
51 }
52
53 is[0] ^= is[1] ^ is[2];
54 }
55
56 if (remain > 16) {
57 is[0] = mix(r64(k) ^ sc[2], r64(k[8..]) ^ is[0] ^ sc[1]);
58 if (remain > 32) {
59 is[0] = mix(r64(k[16..]) ^ sc[2], r64(k[24..]) ^ is[0]);
60 }
61 }
62
63 a = r64(input[len - 16 ..]);
64 b = r64(input[len - 8 ..]);
65 }
66
67 a ^= sc[1];
68 b ^= is[0];
69 mum(&a, &b);
70 return mix(a ^ sc[0] ^ len, b ^ sc[1]);
71}
72
73test "RapidHash.hash" {
74 const bytes: []const u8 = "abcdefgh" ** 128;
75
76 const sizes: [13]u64 = .{ 0, 1, 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };
77
78 const outcomes: [13]u64 = .{
79 0x5a6ef77074ebc84b,
80 0xc11328477bc0f5d1,
81 0x5644ac035e40d569,
82 0x347080fbf5fcd81,
83 0x56b66b8dc802bcc,
84 0xb6bf9055973aac7c,
85 0xed56d62eead1e402,
86 0xc19072d767da8ffb,
87 0x89bb40a9928a4f0d,
88 0xe0af7c5e7b6e29fd,
89 0x9a3ed35fbedfa11a,
90 0x4c684b2119ca19fb,
91 0x4b575f5bf25600d6,
92 };
93
94 var success: bool = true;
95 for (sizes, outcomes) |s, e| {
96 const r = hash(RAPID_SEED, bytes[0..s]);
97
98 expectEqual(e, r) catch |err| {
99 std.debug.print("Failed on {d}: {!}\n", .{ s, err });
100 success = false;
101 };
102 }
103 try expect(success);
104}
105
106inline fn mum(a: *u64, b: *u64) void {
107 const r = @as(u128, a.*) * b.*;
108 a.* = @truncate(r);
109 b.* = @truncate(r >> 64);
110}
111
112inline fn mix(a: u64, b: u64) u64 {
113 var copy_a = a;
114 var copy_b = b;
115 mum(&copy_a, &copy_b);
116 return copy_a ^ copy_b;
117}
118
119inline fn r64(p: []const u8) u64 {
120 return readInt(u64, p[0..8], .little);
121}
122
123inline fn r32(p: []const u8) u64 {
124 return readInt(u32, p[0..4], .little);
125}
lib/std/hash/benchmark.zig+17-1
...@@ -59,6 +59,12 @@ const hashes = [_]Hash{...@@ -59,6 +59,12 @@ const hashes = [_]Hash{
59 .ty = hash.crc.Crc32,59 .ty = hash.crc.Crc32,
60 .name = "crc32",60 .name = "crc32",
61 },61 },
62 Hash{
63 .ty = hash.RapidHash,
64 .name = "rapidhash",
65 .has_iterative_api = false,
66 .init_u64 = 0,
67 },
62 Hash{68 Hash{
63 .ty = hash.CityHash32,69 .ty = hash.CityHash32,
64 .name = "cityhash-32",70 .name = "cityhash-32",
...@@ -329,6 +335,7 @@ fn usage() void {...@@ -329,6 +335,7 @@ fn usage() void {
329 \\ --count [int]335 \\ --count [int]
330 \\ --key-size [int]336 \\ --key-size [int]
331 \\ --iterative-only337 \\ --iterative-only
338 \\ --small-key-only
332 \\ --help339 \\ --help
333 \\340 \\
334 , .{});341 , .{});
...@@ -349,6 +356,7 @@ pub fn main() !void {...@@ -349,6 +356,7 @@ pub fn main() !void {
349 var count: usize = mode(128 * MiB);356 var count: usize = mode(128 * MiB);
350 var key_size: ?usize = null;357 var key_size: ?usize = null;
351 var seed: u32 = 0;358 var seed: u32 = 0;
359 var test_small_key_only = false;
352 var test_iterative_only = false;360 var test_iterative_only = false;
353 var test_arrays = false;361 var test_arrays = false;
354362
...@@ -399,6 +407,8 @@ pub fn main() !void {...@@ -399,6 +407,8 @@ pub fn main() !void {
399 }407 }
400 } else if (std.mem.eql(u8, args[i], "--iterative-only")) {408 } else if (std.mem.eql(u8, args[i], "--iterative-only")) {
401 test_iterative_only = true;409 test_iterative_only = true;
410 } else if (std.mem.eql(u8, args[i], "--small-key-only")) {
411 test_small_key_only = true;
402 } else if (std.mem.eql(u8, args[i], "--include-array")) {412 } else if (std.mem.eql(u8, args[i], "--include-array")) {
403 test_arrays = true;413 test_arrays = true;
404 } else if (std.mem.eql(u8, args[i], "--help")) {414 } else if (std.mem.eql(u8, args[i], "--help")) {
...@@ -410,6 +420,12 @@ pub fn main() !void {...@@ -410,6 +420,12 @@ pub fn main() !void {
410 }420 }
411 }421 }
412422
423 if (test_iterative_only and test_small_key_only) {
424 try stdout.print("Cannot use iterative-only and small-key-only together!\n", .{});
425 usage();
426 std.process.exit(1);
427 }
428
413 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;429 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
414 defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");430 defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
415 const allocator = gpa.allocator();431 const allocator = gpa.allocator();
...@@ -421,7 +437,7 @@ pub fn main() !void {...@@ -421,7 +437,7 @@ pub fn main() !void {
421437
422 // Always reseed prior to every call so we are hashing the same buffer contents.438 // Always reseed prior to every call so we are hashing the same buffer contents.
423 // This allows easier comparison between different implementations.439 // This allows easier comparison between different implementations.
424 if (H.has_iterative_api) {440 if (H.has_iterative_api and !test_small_key_only) {
425 prng.seed(seed);441 prng.seed(seed);
426 const result = try benchmarkHash(H, count, allocator);442 const result = try benchmarkHash(H, count, allocator);
427 try stdout.print(" iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash });443 try stdout.print(" iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash });