mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Fix std.hash benchmarks (#15917)
This commit is contained in:
parent
3faf376b08
commit
879f0b9cee
@ -34,19 +34,24 @@ const hashes = [_]Crypto{
|
|||||||
Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
|
Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const block_size: usize = 8 * 8192;
|
||||||
|
|
||||||
pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
|
pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
|
||||||
|
const blocks_count = bytes / block_size;
|
||||||
|
var block: [block_size]u8 = undefined;
|
||||||
|
random.bytes(&block);
|
||||||
|
|
||||||
var h = Hash.init(.{});
|
var h = Hash.init(.{});
|
||||||
|
|
||||||
var block: [Hash.digest_length]u8 = undefined;
|
|
||||||
random.bytes(block[0..]);
|
|
||||||
|
|
||||||
var offset: usize = 0;
|
|
||||||
var timer = try Timer.start();
|
var timer = try Timer.start();
|
||||||
const start = timer.lap();
|
const start = timer.lap();
|
||||||
while (offset < bytes) : (offset += block.len) {
|
for (0..blocks_count) |_| {
|
||||||
h.update(block[0..]);
|
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 end = timer.read();
|
||||||
|
|
||||||
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
||||||
|
|||||||
@ -47,7 +47,6 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
|
|||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
const block_length = 64;
|
const block_length = 64;
|
||||||
const digest_length = 64;
|
|
||||||
const key_length = 16;
|
const key_length = 16;
|
||||||
|
|
||||||
v0: u64,
|
v0: u64,
|
||||||
@ -56,7 +55,7 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
|
|||||||
v3: u64,
|
v3: u64,
|
||||||
msg_len: u8,
|
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 k0 = mem.readIntLittle(u64, key[0..8]);
|
||||||
const k1 = mem.readIntLittle(u64, key[8..16]);
|
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;
|
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);
|
std.debug.assert(b.len % 8 == 0);
|
||||||
|
|
||||||
var off: usize = 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);
|
self.msg_len +%= @truncate(u8, b.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn peek(self: Self) [digest_length]u8 {
|
fn final(self: *Self, b: []const u8) T {
|
||||||
var copy = self;
|
|
||||||
return copy.finalResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn final(self: *Self, b: []const u8) T {
|
|
||||||
std.debug.assert(b.len < 8);
|
std.debug.assert(b.len < 8);
|
||||||
|
|
||||||
self.msg_len +%= @truncate(u8, b.len);
|
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;
|
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 {
|
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;
|
self.v3 ^= m;
|
||||||
|
|
||||||
comptime var i: usize = 0;
|
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));
|
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);
|
const aligned_len = msg.len - (msg.len % 8);
|
||||||
var c = Self.init(key);
|
var c = Self.init(key);
|
||||||
@call(.always_inline, update, .{ &c, msg[0..aligned_len] });
|
@call(.always_inline, update, .{ &c, msg[0..aligned_len] });
|
||||||
|
|||||||
@ -17,11 +17,22 @@ const Hash = struct {
|
|||||||
ty: type,
|
ty: type,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
has_iterative_api: bool = true,
|
has_iterative_api: bool = true,
|
||||||
|
has_crypto_api: bool = false,
|
||||||
init_u8s: ?[]const u8 = null,
|
init_u8s: ?[]const u8 = null,
|
||||||
init_u64: ?u64 = null,
|
init_u64: ?u64 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hashes = [_]Hash{
|
const hashes = [_]Hash{
|
||||||
|
Hash{
|
||||||
|
.ty = hash.XxHash64,
|
||||||
|
.name = "xxhash64",
|
||||||
|
.init_u64 = 0,
|
||||||
|
},
|
||||||
|
Hash{
|
||||||
|
.ty = hash.XxHash32,
|
||||||
|
.name = "xxhash32",
|
||||||
|
.init_u64 = 0,
|
||||||
|
},
|
||||||
Hash{
|
Hash{
|
||||||
.ty = hash.Wyhash,
|
.ty = hash.Wyhash,
|
||||||
.name = "wyhash",
|
.name = "wyhash",
|
||||||
@ -68,6 +79,18 @@ const hashes = [_]Hash{
|
|||||||
.name = "murmur3-32",
|
.name = "murmur3-32",
|
||||||
.has_iterative_api = false,
|
.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 {
|
const Result = struct {
|
||||||
@ -76,11 +99,17 @@ const Result = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const block_size: usize = 8 * 8192;
|
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: {
|
var h = blk: {
|
||||||
if (H.init_u8s) |init| {
|
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| {
|
if (H.init_u64) |init| {
|
||||||
break :blk H.ty.init(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();
|
break :blk H.ty.init();
|
||||||
};
|
};
|
||||||
|
|
||||||
var block: [block_size]u8 = undefined;
|
|
||||||
random.bytes(block[0..]);
|
|
||||||
|
|
||||||
var offset: usize = 0;
|
|
||||||
var timer = try Timer.start();
|
var timer = try Timer.start();
|
||||||
const start = timer.lap();
|
const start = timer.lap();
|
||||||
while (offset < bytes) : (offset += block.len) {
|
for (0..blocks_count) |i| {
|
||||||
h.update(block[0..]);
|
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 end = timer.read();
|
||||||
|
|
||||||
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
||||||
const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
|
const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
|
||||||
|
|
||||||
return Result{
|
return Result{
|
||||||
.hash = h.final(),
|
.hash = final,
|
||||||
.throughput = throughput,
|
.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 {
|
||||||
const key_count = bytes / key_size;
|
var blocks = try allocator.alloc(u8, bytes);
|
||||||
var block: [block_size]u8 = undefined;
|
defer allocator.free(blocks);
|
||||||
random.bytes(block[0..]);
|
random.bytes(blocks);
|
||||||
|
|
||||||
|
const key_count = bytes / key_size;
|
||||||
|
|
||||||
var i: usize = 0;
|
|
||||||
var timer = try Timer.start();
|
var timer = try Timer.start();
|
||||||
const start = timer.lap();
|
const start = timer.lap();
|
||||||
|
|
||||||
var sum: u64 = 0;
|
var sum: u64 = 0;
|
||||||
while (i < key_count) : (i += 1) {
|
for (0..key_count) |i| {
|
||||||
const small_key = block[0..key_size];
|
const small_key = blocks[i * key_size ..][0..key_size];
|
||||||
sum +%= blk: {
|
const final = blk: {
|
||||||
if (H.init_u8s) |init| {
|
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| {
|
if (H.init_u64) |init| {
|
||||||
break :blk H.ty.hash(init, small_key);
|
break :blk H.ty.hash(init, small_key);
|
||||||
}
|
}
|
||||||
break :blk H.ty.hash(small_key);
|
break :blk H.ty.hash(small_key);
|
||||||
};
|
};
|
||||||
|
sum +%= final;
|
||||||
}
|
}
|
||||||
const end = timer.read();
|
const end = timer.read();
|
||||||
|
|
||||||
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
||||||
const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
|
const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
|
||||||
|
|
||||||
|
std.mem.doNotOptimizeAway(sum);
|
||||||
|
|
||||||
return Result{
|
return Result{
|
||||||
.hash = sum,
|
.hash = sum,
|
||||||
.throughput = throughput,
|
.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| {
|
inline for (hashes) |H| {
|
||||||
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
|
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
|
||||||
if (!test_iterative_only or H.has_iterative_api) {
|
if (!test_iterative_only or H.has_iterative_api) {
|
||||||
@ -236,13 +276,13 @@ pub fn main() !void {
|
|||||||
// This allows easier comparison between different implementations.
|
// This allows easier comparison between different implementations.
|
||||||
if (H.has_iterative_api) {
|
if (H.has_iterative_api) {
|
||||||
prng.seed(seed);
|
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 });
|
try stdout.print(" iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!test_iterative_only) {
|
if (!test_iterative_only) {
|
||||||
prng.seed(seed);
|
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 });
|
try stdout.print(" small keys: {:5} MiB/s [{x:0<16}]\n", .{ result_small.throughput / (1 * MiB), result_small.hash });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,8 +126,8 @@ pub const XxHash64 = struct {
|
|||||||
return b +% prime_4;
|
return b +% prime_4;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(input: []const u8) u64 {
|
pub fn hash(seed: u64, input: []const u8) u64 {
|
||||||
var hasher = XxHash64.init(0);
|
var hasher = XxHash64.init(seed);
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
return hasher.final();
|
return hasher.final();
|
||||||
}
|
}
|
||||||
@ -236,8 +236,8 @@ pub const XxHash32 = struct {
|
|||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(input: []const u8) u32 {
|
pub fn hash(seed: u32, input: []const u8) u32 {
|
||||||
var hasher = XxHash32.init(0);
|
var hasher = XxHash32.init(seed);
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
return hasher.final();
|
return hasher.final();
|
||||||
}
|
}
|
||||||
@ -246,23 +246,23 @@ pub const XxHash32 = struct {
|
|||||||
test "xxhash64" {
|
test "xxhash64" {
|
||||||
const hash = XxHash64.hash;
|
const hash = XxHash64.hash;
|
||||||
|
|
||||||
try expectEqual(hash(""), 0xef46db3751d8e999);
|
try expectEqual(hash(0, ""), 0xef46db3751d8e999);
|
||||||
try expectEqual(hash("a"), 0xd24ec4f1a98c6e5b);
|
try expectEqual(hash(0, "a"), 0xd24ec4f1a98c6e5b);
|
||||||
try expectEqual(hash("abc"), 0x44bc2cf5ad770999);
|
try expectEqual(hash(0, "abc"), 0x44bc2cf5ad770999);
|
||||||
try expectEqual(hash("message digest"), 0x066ed728fceeb3be);
|
try expectEqual(hash(0, "message digest"), 0x066ed728fceeb3be);
|
||||||
try expectEqual(hash("abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c);
|
try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c);
|
||||||
try expectEqual(hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814);
|
try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814);
|
||||||
try expectEqual(hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d);
|
try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "xxhash32" {
|
test "xxhash32" {
|
||||||
const hash = XxHash32.hash;
|
const hash = XxHash32.hash;
|
||||||
|
|
||||||
try expectEqual(hash(""), 0x02cc5d05);
|
try expectEqual(hash(0, ""), 0x02cc5d05);
|
||||||
try expectEqual(hash("a"), 0x550d7456);
|
try expectEqual(hash(0, "a"), 0x550d7456);
|
||||||
try expectEqual(hash("abc"), 0x32d153ff);
|
try expectEqual(hash(0, "abc"), 0x32d153ff);
|
||||||
try expectEqual(hash("message digest"), 0x7c948494);
|
try expectEqual(hash(0, "message digest"), 0x7c948494);
|
||||||
try expectEqual(hash("abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f);
|
try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f);
|
||||||
try expectEqual(hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64);
|
try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64);
|
||||||
try expectEqual(hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475);
|
try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user