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