| 1 | // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig |
| 2 | |
| 3 | const builtin = @import("builtin"); |
| 4 | |
| 5 | const std = @import("std"); |
| 6 | const Io = std.Io; |
| 7 | const time = std.time; |
| 8 | const Random = std.Random; |
| 9 | |
| 10 | const KiB = 1024; |
| 11 | const MiB = 1024 * KiB; |
| 12 | const GiB = 1024 * MiB; |
| 13 | |
| 14 | const Rng = struct { |
| 15 | ty: type, |
| 16 | name: []const u8, |
| 17 | init_u8s: ?[]const u8 = null, |
| 18 | init_u64: ?u64 = null, |
| 19 | }; |
| 20 | |
| 21 | const prngs = [_]Rng{ |
| 22 | Rng{ |
| 23 | .ty = Random.Isaac64, |
| 24 | .name = "isaac64", |
| 25 | .init_u64 = 0, |
| 26 | }, |
| 27 | Rng{ |
| 28 | .ty = Random.Pcg, |
| 29 | .name = "pcg", |
| 30 | .init_u64 = 0, |
| 31 | }, |
| 32 | Rng{ |
| 33 | .ty = Random.RomuTrio, |
| 34 | .name = "romutrio", |
| 35 | .init_u64 = 0, |
| 36 | }, |
| 37 | Rng{ |
| 38 | .ty = Random.Sfc64, |
| 39 | .name = "sfc64", |
| 40 | .init_u64 = 0, |
| 41 | }, |
| 42 | Rng{ |
| 43 | .ty = Random.Xoroshiro128, |
| 44 | .name = "xoroshiro128", |
| 45 | .init_u64 = 0, |
| 46 | }, |
| 47 | Rng{ |
| 48 | .ty = Random.Xoshiro256, |
| 49 | .name = "xoshiro256", |
| 50 | .init_u64 = 0, |
| 51 | }, |
| 52 | }; |
| 53 | |
| 54 | const csprngs = [_]Rng{ |
| 55 | Rng{ |
| 56 | .ty = Random.Ascon, |
| 57 | .name = "ascon", |
| 58 | .init_u8s = &@as([32]u8, @splat(0)), |
| 59 | }, |
| 60 | Rng{ |
| 61 | .ty = Random.ChaCha, |
| 62 | .name = "chacha", |
| 63 | .init_u8s = &@as([32]u8, @splat(0)), |
| 64 | }, |
| 65 | }; |
| 66 | |
| 67 | const Result = struct { |
| 68 | throughput: u64, |
| 69 | }; |
| 70 | |
| 71 | const long_block_size: usize = 8 * 8192; |
| 72 | const short_block_size: usize = 8; |
| 73 | |
| 74 | pub fn benchTime(io: Io) i96 { |
| 75 | return Io.Clock.awake.now(io).nanoseconds; |
| 76 | } |
| 77 | |
| 78 | pub fn benchmark(comptime H: anytype, io: Io, bytes: usize, comptime block_size: usize) !Result { |
| 79 | var rng = blk: { |
| 80 | if (H.init_u8s) |init| { |
| 81 | break :blk H.ty.init(init[0..].*); |
| 82 | } |
| 83 | if (H.init_u64) |init| { |
| 84 | break :blk H.ty.init(init); |
| 85 | } |
| 86 | break :blk H.ty.init(); |
| 87 | }; |
| 88 | |
| 89 | var block: [block_size]u8 = undefined; |
| 90 | |
| 91 | var offset: usize = 0; |
| 92 | const start = benchTime(io); |
| 93 | while (offset < bytes) : (offset += block.len) { |
| 94 | rng.fill(block[0..]); |
| 95 | } |
| 96 | const end = benchTime(io); |
| 97 | |
| 98 | const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s; |
| 99 | const throughput = @as(u64, @intFromFloat(@as(f64, @floatFromInt(bytes)) / elapsed_s)); |
| 100 | |
| 101 | std.debug.assert(rng.random().int(u64) != 0); |
| 102 | |
| 103 | return Result{ |
| 104 | .throughput = throughput, |
| 105 | }; |
| 106 | } |
| 107 | |
| 108 | fn usage() void { |
| 109 | std.debug.print( |
| 110 | \\throughput_test [options] |
| 111 | \\ |
| 112 | \\Options: |
| 113 | \\ --filter [test-name] |
| 114 | \\ --count [int] |
| 115 | \\ --prngs-only |
| 116 | \\ --csprngs-only |
| 117 | \\ --short-only |
| 118 | \\ --long-only |
| 119 | \\ --help |
| 120 | \\ |
| 121 | , .{}); |
| 122 | } |
| 123 | |
| 124 | fn mode(comptime x: comptime_int) comptime_int { |
| 125 | return if (builtin.mode == .debug) x / 64 else x; |
| 126 | } |
| 127 | |
| 128 | pub fn main(init: std.process.Init) !void { |
| 129 | const io = init.io; |
| 130 | const arena = init.arena.allocator(); |
| 131 | |
| 132 | var stdout_buffer: [0x100]u8 = undefined; |
| 133 | var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); |
| 134 | const stdout = &stdout_writer.interface; |
| 135 | |
| 136 | const args = try init.minimal.args.toSlice(arena); |
| 137 | |
| 138 | var filter: ?[]const u8 = null; |
| 139 | var count: usize = mode(128 * MiB); |
| 140 | var bench_prngs = true; |
| 141 | var bench_csprngs = true; |
| 142 | var bench_long = true; |
| 143 | var bench_short = true; |
| 144 | |
| 145 | var i: usize = 1; |
| 146 | while (i < args.len) : (i += 1) { |
| 147 | if (std.mem.eql(u8, args[i], "--mode")) { |
| 148 | try stdout.print("{}\n", .{builtin.mode}); |
| 149 | try stdout.flush(); |
| 150 | return; |
| 151 | } else if (std.mem.eql(u8, args[i], "--filter")) { |
| 152 | i += 1; |
| 153 | if (i == args.len) { |
| 154 | usage(); |
| 155 | std.process.exit(1); |
| 156 | } |
| 157 | |
| 158 | filter = args[i]; |
| 159 | } else if (std.mem.eql(u8, args[i], "--count")) { |
| 160 | i += 1; |
| 161 | if (i == args.len) { |
| 162 | usage(); |
| 163 | std.process.exit(1); |
| 164 | } |
| 165 | |
| 166 | const c = try std.fmt.parseUnsigned(usize, args[i], 10); |
| 167 | count = c * MiB; |
| 168 | } else if (std.mem.eql(u8, args[i], "--csprngs-only")) { |
| 169 | bench_prngs = false; |
| 170 | } else if (std.mem.eql(u8, args[i], "--prngs-only")) { |
| 171 | bench_csprngs = false; |
| 172 | } else if (std.mem.eql(u8, args[i], "--short-only")) { |
| 173 | bench_long = false; |
| 174 | } else if (std.mem.eql(u8, args[i], "--long-only")) { |
| 175 | bench_short = false; |
| 176 | } else if (std.mem.eql(u8, args[i], "--help")) { |
| 177 | usage(); |
| 178 | return; |
| 179 | } else { |
| 180 | usage(); |
| 181 | std.process.exit(1); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | if (bench_prngs) { |
| 186 | if (bench_long) { |
| 187 | inline for (prngs) |R| { |
| 188 | if (filter == null or std.mem.find(u8, R.name, filter.?) != null) { |
| 189 | try stdout.print("{s} (long outputs)\n", .{R.name}); |
| 190 | try stdout.flush(); |
| 191 | |
| 192 | const result_long = try benchmark(R, io, count, long_block_size); |
| 193 | try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)}); |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | if (bench_short) { |
| 198 | inline for (prngs) |R| { |
| 199 | if (filter == null or std.mem.find(u8, R.name, filter.?) != null) { |
| 200 | try stdout.print("{s} (short outputs)\n", .{R.name}); |
| 201 | try stdout.flush(); |
| 202 | |
| 203 | const result_short = try benchmark(R, io, count, short_block_size); |
| 204 | try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)}); |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | if (bench_csprngs) { |
| 210 | if (bench_long) { |
| 211 | inline for (csprngs) |R| { |
| 212 | if (filter == null or std.mem.find(u8, R.name, filter.?) != null) { |
| 213 | try stdout.print("{s} (cryptographic, long outputs)\n", .{R.name}); |
| 214 | try stdout.flush(); |
| 215 | |
| 216 | const result_long = try benchmark(R, io, count, long_block_size); |
| 217 | try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)}); |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | if (bench_short) { |
| 222 | inline for (csprngs) |R| { |
| 223 | if (filter == null or std.mem.find(u8, R.name, filter.?) != null) { |
| 224 | try stdout.print("{s} (cryptographic, short outputs)\n", .{R.name}); |
| 225 | try stdout.flush(); |
| 226 | |
| 227 | const result_short = try benchmark(R, io, count, short_block_size); |
| 228 | try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)}); |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | try stdout.flush(); |
| 234 | } |