Commit 879f0b9cee

Frank Denis <124872+jedisct1@users.noreply.github.com>
2023-06-02 20:08:28
Fix std.hash benchmarks (#15917)
1 parent 3faf376
Changed files (4)
lib/std/crypto/benchmark.zig
@@ -34,19 +34,24 @@ const hashes = [_]Crypto{
     Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
 };
 
+const block_size: usize = 8 * 8192;
+
 pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
-    var h = Hash.init(.{});
+    const blocks_count = bytes / block_size;
+    var block: [block_size]u8 = undefined;
+    random.bytes(&block);
 
-    var block: [Hash.digest_length]u8 = undefined;
-    random.bytes(block[0..]);
+    var h = Hash.init(.{});
 
-    var offset: usize = 0;
     var timer = try Timer.start();
     const start = timer.lap();
-    while (offset < bytes) : (offset += block.len) {
-        h.update(block[0..]);
+    for (0..blocks_count) |_| {
+        h.update(&block);
     }
-    mem.doNotOptimizeAway(&h);
+    var final: [Hash.digest_length]u8 = undefined;
+    h.final(&final);
+    std.mem.doNotOptimizeAway(final);
+
     const end = timer.read();
 
     const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
lib/std/crypto/siphash.zig
@@ -47,7 +47,6 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
     return struct {
         const Self = @This();
         const block_length = 64;
-        const digest_length = 64;
         const key_length = 16;
 
         v0: u64,
@@ -56,7 +55,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
         v3: u64,
         msg_len: u8,
 
-        pub fn init(key: *const [key_length]u8) Self {
+        fn init(key: *const [key_length]u8) Self {
             const k0 = mem.readIntLittle(u64, key[0..8]);
             const k1 = mem.readIntLittle(u64, key[8..16]);
 
@@ -75,7 +74,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
             return d;
         }
 
-        pub fn update(self: *Self, b: []const u8) void {
+        fn update(self: *Self, b: []const u8) void {
             std.debug.assert(b.len % 8 == 0);
 
             var off: usize = 0;
@@ -87,12 +86,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
             self.msg_len +%= @truncate(u8, b.len);
         }
 
-        pub fn peek(self: Self) [digest_length]u8 {
-            var copy = self;
-            return copy.finalResult();
-        }
-
-        pub fn final(self: *Self, b: []const u8) T {
+        fn final(self: *Self, b: []const u8) T {
             std.debug.assert(b.len < 8);
 
             self.msg_len +%= @truncate(u8, b.len);
@@ -129,14 +123,8 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
             return (@as(u128, b2) << 64) | b1;
         }
 
-        pub fn finalResult(self: *Self) [digest_length]u8 {
-            var result: [digest_length]u8 = undefined;
-            self.final(&result);
-            return result;
-        }
-
         fn round(self: *Self, b: [8]u8) void {
-            const m = mem.readIntLittle(u64, b[0..8]);
+            const m = mem.readIntLittle(u64, &b);
             self.v3 ^= m;
 
             comptime var i: usize = 0;
@@ -164,7 +152,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
             d.v2 = math.rotl(u64, d.v2, @as(u64, 32));
         }
 
-        pub fn hash(msg: []const u8, key: *const [key_length]u8) T {
+        fn hash(msg: []const u8, key: *const [key_length]u8) T {
             const aligned_len = msg.len - (msg.len % 8);
             var c = Self.init(key);
             @call(.always_inline, update, .{ &c, msg[0..aligned_len] });
lib/std/hash/benchmark.zig
@@ -17,11 +17,22 @@ const Hash = struct {
     ty: type,
     name: []const u8,
     has_iterative_api: bool = true,
+    has_crypto_api: bool = false,
     init_u8s: ?[]const u8 = null,
     init_u64: ?u64 = null,
 };
 
 const hashes = [_]Hash{
+    Hash{
+        .ty = hash.XxHash64,
+        .name = "xxhash64",
+        .init_u64 = 0,
+    },
+    Hash{
+        .ty = hash.XxHash32,
+        .name = "xxhash32",
+        .init_u64 = 0,
+    },
     Hash{
         .ty = hash.Wyhash,
         .name = "wyhash",
@@ -68,6 +79,18 @@ const hashes = [_]Hash{
         .name = "murmur3-32",
         .has_iterative_api = false,
     },
+    Hash{
+        .ty = hash.SipHash64(1, 3),
+        .name = "siphash64",
+        .has_crypto_api = true,
+        .init_u8s = &[_]u8{0} ** 16,
+    },
+    Hash{
+        .ty = hash.SipHash128(1, 3),
+        .name = "siphash128",
+        .has_crypto_api = true,
+        .init_u8s = &[_]u8{0} ** 16,
+    },
 };
 
 const Result = struct {
@@ -76,11 +99,17 @@ const Result = struct {
 };
 
 const block_size: usize = 8 * 8192;
+const alignment: usize = 64;
+
+pub fn benchmarkHash(comptime H: anytype, bytes: usize, allocator: std.mem.Allocator) !Result {
+    const blocks_count = bytes / block_size;
+    var blocks = try allocator.alloc(u8, block_size + alignment * (blocks_count - 1));
+    defer allocator.free(blocks);
+    random.bytes(blocks);
 
-pub fn benchmarkHash(comptime H: anytype, bytes: usize) !Result {
     var h = blk: {
         if (H.init_u8s) |init| {
-            break :blk H.ty.init(init);
+            break :blk H.ty.init(init[0..H.ty.key_length]);
         }
         if (H.init_u64) |init| {
             break :blk H.ty.init(init);
@@ -88,53 +117,60 @@ pub fn benchmarkHash(comptime H: anytype, bytes: usize) !Result {
         break :blk H.ty.init();
     };
 
-    var block: [block_size]u8 = undefined;
-    random.bytes(block[0..]);
-
-    var offset: usize = 0;
     var timer = try Timer.start();
     const start = timer.lap();
-    while (offset < bytes) : (offset += block.len) {
-        h.update(block[0..]);
+    for (0..blocks_count) |i| {
+        h.update(blocks[i * alignment ..][0..block_size]);
     }
+    const final = if (H.has_crypto_api) @truncate(u64, h.finalInt()) else h.final();
+    std.mem.doNotOptimizeAway(final);
+
     const end = timer.read();
 
     const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
     const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
 
     return Result{
-        .hash = h.final(),
+        .hash = final,
         .throughput = throughput,
     };
 }
 
-pub fn benchmarkHashSmallKeys(comptime H: anytype, key_size: usize, bytes: usize) !Result {
+pub fn benchmarkHashSmallKeys(comptime H: anytype, key_size: usize, bytes: usize, allocator: std.mem.Allocator) !Result {
+    var blocks = try allocator.alloc(u8, bytes);
+    defer allocator.free(blocks);
+    random.bytes(blocks);
+
     const key_count = bytes / key_size;
-    var block: [block_size]u8 = undefined;
-    random.bytes(block[0..]);
 
-    var i: usize = 0;
     var timer = try Timer.start();
     const start = timer.lap();
 
     var sum: u64 = 0;
-    while (i < key_count) : (i += 1) {
-        const small_key = block[0..key_size];
-        sum +%= blk: {
+    for (0..key_count) |i| {
+        const small_key = blocks[i * key_size ..][0..key_size];
+        const final = blk: {
             if (H.init_u8s) |init| {
-                break :blk H.ty.hash(init, small_key);
+                if (H.has_crypto_api) {
+                    break :blk @truncate(u64, H.ty.toInt(small_key, init[0..H.ty.key_length]));
+                } else {
+                    break :blk H.ty.hash(init, small_key);
+                }
             }
             if (H.init_u64) |init| {
                 break :blk H.ty.hash(init, small_key);
             }
             break :blk H.ty.hash(small_key);
         };
+        sum +%= final;
     }
     const end = timer.read();
 
     const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
     const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
 
+    std.mem.doNotOptimizeAway(sum);
+
     return Result{
         .hash = sum,
         .throughput = throughput,
@@ -227,6 +263,10 @@ pub fn main() !void {
         }
     }
 
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+    defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
+    const allocator = gpa.allocator();
+
     inline for (hashes) |H| {
         if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
             if (!test_iterative_only or H.has_iterative_api) {
@@ -236,13 +276,13 @@ pub fn main() !void {
                 // This allows easier comparison between different implementations.
                 if (H.has_iterative_api) {
                     prng.seed(seed);
-                    const result = try benchmarkHash(H, count);
+                    const result = try benchmarkHash(H, count, allocator);
                     try stdout.print("   iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash });
                 }
 
                 if (!test_iterative_only) {
                     prng.seed(seed);
-                    const result_small = try benchmarkHashSmallKeys(H, key_size, count);
+                    const result_small = try benchmarkHashSmallKeys(H, key_size, count, allocator);
                     try stdout.print("  small keys: {:5} MiB/s [{x:0<16}]\n", .{ result_small.throughput / (1 * MiB), result_small.hash });
                 }
             }
lib/std/hash/xxhash.zig
@@ -126,8 +126,8 @@ pub const XxHash64 = struct {
         return b +% prime_4;
     }
 
-    pub fn hash(input: []const u8) u64 {
-        var hasher = XxHash64.init(0);
+    pub fn hash(seed: u64, input: []const u8) u64 {
+        var hasher = XxHash64.init(seed);
         hasher.update(input);
         return hasher.final();
     }
@@ -236,8 +236,8 @@ pub const XxHash32 = struct {
         return acc;
     }
 
-    pub fn hash(input: []const u8) u32 {
-        var hasher = XxHash32.init(0);
+    pub fn hash(seed: u32, input: []const u8) u32 {
+        var hasher = XxHash32.init(seed);
         hasher.update(input);
         return hasher.final();
     }
@@ -246,23 +246,23 @@ pub const XxHash32 = struct {
 test "xxhash64" {
     const hash = XxHash64.hash;
 
-    try expectEqual(hash(""), 0xef46db3751d8e999);
-    try expectEqual(hash("a"), 0xd24ec4f1a98c6e5b);
-    try expectEqual(hash("abc"), 0x44bc2cf5ad770999);
-    try expectEqual(hash("message digest"), 0x066ed728fceeb3be);
-    try expectEqual(hash("abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c);
-    try expectEqual(hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814);
-    try expectEqual(hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d);
+    try expectEqual(hash(0, ""), 0xef46db3751d8e999);
+    try expectEqual(hash(0, "a"), 0xd24ec4f1a98c6e5b);
+    try expectEqual(hash(0, "abc"), 0x44bc2cf5ad770999);
+    try expectEqual(hash(0, "message digest"), 0x066ed728fceeb3be);
+    try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c);
+    try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814);
+    try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d);
 }
 
 test "xxhash32" {
     const hash = XxHash32.hash;
 
-    try expectEqual(hash(""), 0x02cc5d05);
-    try expectEqual(hash("a"), 0x550d7456);
-    try expectEqual(hash("abc"), 0x32d153ff);
-    try expectEqual(hash("message digest"), 0x7c948494);
-    try expectEqual(hash("abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f);
-    try expectEqual(hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64);
-    try expectEqual(hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475);
+    try expectEqual(hash(0, ""), 0x02cc5d05);
+    try expectEqual(hash(0, "a"), 0x550d7456);
+    try expectEqual(hash(0, "abc"), 0x32d153ff);
+    try expectEqual(hash(0, "message digest"), 0x7c948494);
+    try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f);
+    try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64);
+    try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475);
 }