authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2023-06-02 20:08:28+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-02 20:08:28+02:00
log879f0b9cee9b409160edf10d8b52f73be2bddb4f
tree4d875ad83f648069cc342aa680eb81d49b9b98f2
parent3faf376b081caa47a98a172fe7c7f63f82b150e4
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Fix std.hash benchmarks (#15917)


4 files changed, 94 insertions(+), 61 deletions(-)

lib/std/crypto/benchmark.zig+12-7
...@@ -34,19 +34,24 @@ const hashes = [_]Crypto{...@@ -34,19 +34,24 @@ const hashes = [_]Crypto{
34 Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },34 Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
35};35};
3636
37const block_size: usize = 8 * 8192;
38
37pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {39pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
38 var h = Hash.init(.{});40 const blocks_count = bytes / block_size;
41 var block: [block_size]u8 = undefined;
42 random.bytes(&block);
3943
40 var block: [Hash.digest_length]u8 = undefined;44 var h = Hash.init(.{});
41 random.bytes(block[0..]);
4245
43 var offset: usize = 0;
44 var timer = try Timer.start();46 var timer = try Timer.start();
45 const start = timer.lap();47 const start = timer.lap();
46 while (offset < bytes) : (offset += block.len) {48 for (0..blocks_count) |_| {
47 h.update(block[0..]);49 h.update(&block);
48 }50 }
49 mem.doNotOptimizeAway(&h);51 var final: [Hash.digest_length]u8 = undefined;
52 h.final(&final);
53 std.mem.doNotOptimizeAway(final);
54
50 const end = timer.read();55 const end = timer.read();
5156
52 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;57 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
lib/std/crypto/siphash.zig+5-17
...@@ -47,7 +47,6 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round...@@ -47,7 +47,6 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
47 return struct {47 return struct {
48 const Self = @This();48 const Self = @This();
49 const block_length = 64;49 const block_length = 64;
50 const digest_length = 64;
51 const key_length = 16;50 const key_length = 16;
5251
53 v0: u64,52 v0: u64,
...@@ -56,7 +55,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round...@@ -56,7 +55,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
56 v3: u64,55 v3: u64,
57 msg_len: u8,56 msg_len: u8,
5857
59 pub fn init(key: *const [key_length]u8) Self {58 fn init(key: *const [key_length]u8) Self {
60 const k0 = mem.readIntLittle(u64, key[0..8]);59 const k0 = mem.readIntLittle(u64, key[0..8]);
61 const k1 = mem.readIntLittle(u64, key[8..16]);60 const k1 = mem.readIntLittle(u64, key[8..16]);
6261
...@@ -75,7 +74,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round...@@ -75,7 +74,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
75 return d;74 return d;
76 }75 }
7776
78 pub fn update(self: *Self, b: []const u8) void {77 fn update(self: *Self, b: []const u8) void {
79 std.debug.assert(b.len % 8 == 0);78 std.debug.assert(b.len % 8 == 0);
8079
81 var off: usize = 0;80 var off: usize = 0;
...@@ -87,12 +86,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round...@@ -87,12 +86,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
87 self.msg_len +%= @truncate(u8, b.len);86 self.msg_len +%= @truncate(u8, b.len);
88 }87 }
8988
90 pub fn peek(self: Self) [digest_length]u8 {89 fn final(self: *Self, b: []const u8) T {
91 var copy = self;
92 return copy.finalResult();
93 }
94
95 pub fn final(self: *Self, b: []const u8) T {
96 std.debug.assert(b.len < 8);90 std.debug.assert(b.len < 8);
9791
98 self.msg_len +%= @truncate(u8, b.len);92 self.msg_len +%= @truncate(u8, b.len);
...@@ -129,14 +123,8 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round...@@ -129,14 +123,8 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
129 return (@as(u128, b2) << 64) | b1;123 return (@as(u128, b2) << 64) | b1;
130 }124 }
131125
132 pub fn finalResult(self: *Self) [digest_length]u8 {
133 var result: [digest_length]u8 = undefined;
134 self.final(&result);
135 return result;
136 }
137
138 fn round(self: *Self, b: [8]u8) void {126 fn round(self: *Self, b: [8]u8) void {
139 const m = mem.readIntLittle(u64, b[0..8]);127 const m = mem.readIntLittle(u64, &b);
140 self.v3 ^= m;128 self.v3 ^= m;
141129
142 comptime var i: usize = 0;130 comptime var i: usize = 0;
...@@ -164,7 +152,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round...@@ -164,7 +152,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
164 d.v2 = math.rotl(u64, d.v2, @as(u64, 32));152 d.v2 = math.rotl(u64, d.v2, @as(u64, 32));
165 }153 }
166154
167 pub fn hash(msg: []const u8, key: *const [key_length]u8) T {155 fn hash(msg: []const u8, key: *const [key_length]u8) T {
168 const aligned_len = msg.len - (msg.len % 8);156 const aligned_len = msg.len - (msg.len % 8);
169 var c = Self.init(key);157 var c = Self.init(key);
170 @call(.always_inline, update, .{ &c, msg[0..aligned_len] });158 @call(.always_inline, update, .{ &c, msg[0..aligned_len] });
lib/std/hash/benchmark.zig+59-19
...@@ -17,11 +17,22 @@ const Hash = struct {...@@ -17,11 +17,22 @@ const Hash = struct {
17 ty: type,17 ty: type,
18 name: []const u8,18 name: []const u8,
19 has_iterative_api: bool = true,19 has_iterative_api: bool = true,
20 has_crypto_api: bool = false,
20 init_u8s: ?[]const u8 = null,21 init_u8s: ?[]const u8 = null,
21 init_u64: ?u64 = null,22 init_u64: ?u64 = null,
22};23};
2324
24const hashes = [_]Hash{25const hashes = [_]Hash{
26 Hash{
27 .ty = hash.XxHash64,
28 .name = "xxhash64",
29 .init_u64 = 0,
30 },
31 Hash{
32 .ty = hash.XxHash32,
33 .name = "xxhash32",
34 .init_u64 = 0,
35 },
25 Hash{36 Hash{
26 .ty = hash.Wyhash,37 .ty = hash.Wyhash,
27 .name = "wyhash",38 .name = "wyhash",
...@@ -68,6 +79,18 @@ const hashes = [_]Hash{...@@ -68,6 +79,18 @@ const hashes = [_]Hash{
68 .name = "murmur3-32",79 .name = "murmur3-32",
69 .has_iterative_api = false,80 .has_iterative_api = false,
70 },81 },
82 Hash{
83 .ty = hash.SipHash64(1, 3),
84 .name = "siphash64",
85 .has_crypto_api = true,
86 .init_u8s = &[_]u8{0} ** 16,
87 },
88 Hash{
89 .ty = hash.SipHash128(1, 3),
90 .name = "siphash128",
91 .has_crypto_api = true,
92 .init_u8s = &[_]u8{0} ** 16,
93 },
71};94};
7295
73const Result = struct {96const Result = struct {
...@@ -76,11 +99,17 @@ const Result = struct {...@@ -76,11 +99,17 @@ const Result = struct {
76};99};
77100
78const block_size: usize = 8 * 8192;101const block_size: usize = 8 * 8192;
102const alignment: usize = 64;
103
104pub fn benchmarkHash(comptime H: anytype, bytes: usize, allocator: std.mem.Allocator) !Result {
105 const blocks_count = bytes / block_size;
106 var blocks = try allocator.alloc(u8, block_size + alignment * (blocks_count - 1));
107 defer allocator.free(blocks);
108 random.bytes(blocks);
79109
80pub fn benchmarkHash(comptime H: anytype, bytes: usize) !Result {
81 var h = blk: {110 var h = blk: {
82 if (H.init_u8s) |init| {111 if (H.init_u8s) |init| {
83 break :blk H.ty.init(init);112 break :blk H.ty.init(init[0..H.ty.key_length]);
84 }113 }
85 if (H.init_u64) |init| {114 if (H.init_u64) |init| {
86 break :blk H.ty.init(init);115 break :blk H.ty.init(init);
...@@ -88,53 +117,60 @@ pub fn benchmarkHash(comptime H: anytype, bytes: usize) !Result {...@@ -88,53 +117,60 @@ pub fn benchmarkHash(comptime H: anytype, bytes: usize) !Result {
88 break :blk H.ty.init();117 break :blk H.ty.init();
89 };118 };
90119
91 var block: [block_size]u8 = undefined;
92 random.bytes(block[0..]);
93
94 var offset: usize = 0;
95 var timer = try Timer.start();120 var timer = try Timer.start();
96 const start = timer.lap();121 const start = timer.lap();
97 while (offset < bytes) : (offset += block.len) {122 for (0..blocks_count) |i| {
98 h.update(block[0..]);123 h.update(blocks[i * alignment ..][0..block_size]);
99 }124 }
125 const final = if (H.has_crypto_api) @truncate(u64, h.finalInt()) else h.final();
126 std.mem.doNotOptimizeAway(final);
127
100 const end = timer.read();128 const end = timer.read();
101129
102 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;130 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
103 const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);131 const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
104132
105 return Result{133 return Result{
106 .hash = h.final(),134 .hash = final,
107 .throughput = throughput,135 .throughput = throughput,
108 };136 };
109}137}
110138
111pub fn benchmarkHashSmallKeys(comptime H: anytype, key_size: usize, bytes: usize) !Result {139pub fn benchmarkHashSmallKeys(comptime H: anytype, key_size: usize, bytes: usize, allocator: std.mem.Allocator) !Result {
140 var blocks = try allocator.alloc(u8, bytes);
141 defer allocator.free(blocks);
142 random.bytes(blocks);
143
112 const key_count = bytes / key_size;144 const key_count = bytes / key_size;
113 var block: [block_size]u8 = undefined;
114 random.bytes(block[0..]);
115145
116 var i: usize = 0;
117 var timer = try Timer.start();146 var timer = try Timer.start();
118 const start = timer.lap();147 const start = timer.lap();
119148
120 var sum: u64 = 0;149 var sum: u64 = 0;
121 while (i < key_count) : (i += 1) {150 for (0..key_count) |i| {
122 const small_key = block[0..key_size];151 const small_key = blocks[i * key_size ..][0..key_size];
123 sum +%= blk: {152 const final = blk: {
124 if (H.init_u8s) |init| {153 if (H.init_u8s) |init| {
125 break :blk H.ty.hash(init, small_key);154 if (H.has_crypto_api) {
155 break :blk @truncate(u64, H.ty.toInt(small_key, init[0..H.ty.key_length]));
156 } else {
157 break :blk H.ty.hash(init, small_key);
158 }
126 }159 }
127 if (H.init_u64) |init| {160 if (H.init_u64) |init| {
128 break :blk H.ty.hash(init, small_key);161 break :blk H.ty.hash(init, small_key);
129 }162 }
130 break :blk H.ty.hash(small_key);163 break :blk H.ty.hash(small_key);
131 };164 };
165 sum +%= final;
132 }166 }
133 const end = timer.read();167 const end = timer.read();
134168
135 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;169 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
136 const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);170 const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
137171
172 std.mem.doNotOptimizeAway(sum);
173
138 return Result{174 return Result{
139 .hash = sum,175 .hash = sum,
140 .throughput = throughput,176 .throughput = throughput,
...@@ -227,6 +263,10 @@ pub fn main() !void {...@@ -227,6 +263,10 @@ pub fn main() !void {
227 }263 }
228 }264 }
229265
266 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
267 defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
268 const allocator = gpa.allocator();
269
230 inline for (hashes) |H| {270 inline for (hashes) |H| {
231 if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {271 if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
232 if (!test_iterative_only or H.has_iterative_api) {272 if (!test_iterative_only or H.has_iterative_api) {
...@@ -236,13 +276,13 @@ pub fn main() !void {...@@ -236,13 +276,13 @@ pub fn main() !void {
236 // This allows easier comparison between different implementations.276 // This allows easier comparison between different implementations.
237 if (H.has_iterative_api) {277 if (H.has_iterative_api) {
238 prng.seed(seed);278 prng.seed(seed);
239 const result = try benchmarkHash(H, count);279 const result = try benchmarkHash(H, count, allocator);
240 try stdout.print(" iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash });280 try stdout.print(" iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash });
241 }281 }
242282
243 if (!test_iterative_only) {283 if (!test_iterative_only) {
244 prng.seed(seed);284 prng.seed(seed);
245 const result_small = try benchmarkHashSmallKeys(H, key_size, count);285 const result_small = try benchmarkHashSmallKeys(H, key_size, count, allocator);
246 try stdout.print(" small keys: {:5} MiB/s [{x:0<16}]\n", .{ result_small.throughput / (1 * MiB), result_small.hash });286 try stdout.print(" small keys: {:5} MiB/s [{x:0<16}]\n", .{ result_small.throughput / (1 * MiB), result_small.hash });
247 }287 }
248 }288 }
lib/std/hash/xxhash.zig+18-18
...@@ -126,8 +126,8 @@ pub const XxHash64 = struct {...@@ -126,8 +126,8 @@ pub const XxHash64 = struct {
126 return b +% prime_4;126 return b +% prime_4;
127 }127 }
128128
129 pub fn hash(input: []const u8) u64 {129 pub fn hash(seed: u64, input: []const u8) u64 {
130 var hasher = XxHash64.init(0);130 var hasher = XxHash64.init(seed);
131 hasher.update(input);131 hasher.update(input);
132 return hasher.final();132 return hasher.final();
133 }133 }
...@@ -236,8 +236,8 @@ pub const XxHash32 = struct {...@@ -236,8 +236,8 @@ pub const XxHash32 = struct {
236 return acc;236 return acc;
237 }237 }
238238
239 pub fn hash(input: []const u8) u32 {239 pub fn hash(seed: u32, input: []const u8) u32 {
240 var hasher = XxHash32.init(0);240 var hasher = XxHash32.init(seed);
241 hasher.update(input);241 hasher.update(input);
242 return hasher.final();242 return hasher.final();
243 }243 }
...@@ -246,23 +246,23 @@ pub const XxHash32 = struct {...@@ -246,23 +246,23 @@ pub const XxHash32 = struct {
246test "xxhash64" {246test "xxhash64" {
247 const hash = XxHash64.hash;247 const hash = XxHash64.hash;
248248
249 try expectEqual(hash(""), 0xef46db3751d8e999);249 try expectEqual(hash(0, ""), 0xef46db3751d8e999);
250 try expectEqual(hash("a"), 0xd24ec4f1a98c6e5b);250 try expectEqual(hash(0, "a"), 0xd24ec4f1a98c6e5b);
251 try expectEqual(hash("abc"), 0x44bc2cf5ad770999);251 try expectEqual(hash(0, "abc"), 0x44bc2cf5ad770999);
252 try expectEqual(hash("message digest"), 0x066ed728fceeb3be);252 try expectEqual(hash(0, "message digest"), 0x066ed728fceeb3be);
253 try expectEqual(hash("abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c);253 try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c);
254 try expectEqual(hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814);254 try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814);
255 try expectEqual(hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d);255 try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d);
256}256}
257257
258test "xxhash32" {258test "xxhash32" {
259 const hash = XxHash32.hash;259 const hash = XxHash32.hash;
260260
261 try expectEqual(hash(""), 0x02cc5d05);261 try expectEqual(hash(0, ""), 0x02cc5d05);
262 try expectEqual(hash("a"), 0x550d7456);262 try expectEqual(hash(0, "a"), 0x550d7456);
263 try expectEqual(hash("abc"), 0x32d153ff);263 try expectEqual(hash(0, "abc"), 0x32d153ff);
264 try expectEqual(hash("message digest"), 0x7c948494);264 try expectEqual(hash(0, "message digest"), 0x7c948494);
265 try expectEqual(hash("abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f);265 try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f);
266 try expectEqual(hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64);266 try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64);
267 try expectEqual(hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475);267 try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475);
268}268}