1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
2
3const builtin = @import("builtin");
4
5const std = @import("std");
6const Io = std.Io;
7const time = std.time;
8const Random = std.Random;
9
10const KiB = 1024;
11const MiB = 1024 * KiB;
12const GiB = 1024 * MiB;
13
14const Rng = struct {
15 ty: type,
16 name: []const u8,
17 init_u8s: ?[]const u8 = null,
18 init_u64: ?u64 = null,
19};
20
21const 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
54const 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
67const Result = struct {
68 throughput: u64,
69};
70
71const long_block_size: usize = 8 * 8192;
72const short_block_size: usize = 8;
73
74pub fn benchTime(io: Io) i96 {
75 return Io.Clock.awake.now(io).nanoseconds;
76}
77
78pub 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
108fn 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
124fn mode(comptime x: comptime_int) comptime_int {
125 return if (builtin.mode == .debug) x / 64 else x;
126}
127
128pub 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}