diff options
Diffstat (limited to 'test/perf.zig')
| -rw-r--r-- | test/perf.zig | 94 |
1 files changed, 59 insertions, 35 deletions
diff --git a/test/perf.zig b/test/perf.zig index 368e6ce1..bf5f0200 100644 --- a/test/perf.zig +++ b/test/perf.zig @@ -166,8 +166,8 @@ pub fn main(init: std.process.Init) !void { /// big a difference has to be before it is real. fn measure(op: Op, fx: Fixture, path: []const u8, reps: usize) !Cell { const warmup = 3; - var samples: std.ArrayList(u64) = .empty; - defer samples.deinit(gpa); + const samples = try gpa.alloc(u64, reps); + defer gpa.free(samples); if (op == .open) { // fresh core per sample: opening is a one-shot, and the second Look at @@ -180,9 +180,9 @@ fn measure(op: Op, fx: Fixture, path: []const u8, reps: usize) !Cell { pump(core); _ = try frame(core); const dt = nowNs() -| t0; - if (n >= warmup) try samples.append(gpa, dt / 1000); + if (n >= warmup) samples[n - warmup] = dt / 1000; } - return summarize(samples.items); + return summarize(samples); } const core = try boot(); @@ -217,9 +217,9 @@ fn measure(op: Op, fx: Fixture, path: []const u8, reps: usize) !Cell { pump(core); _ = try frame(core); const dt = nowNs() -| t0; - if (n >= warmup) try samples.append(gpa, dt / 1000); + if (n >= warmup) samples[n - warmup] = dt / 1000; } - return summarize(samples.items); + return summarize(samples); } /// Park the cursor and the view at sample `n`'s position, walked across the @@ -291,22 +291,39 @@ fn summarize(samples: []u64) Cell { /// lines get their width from a comment tail — the alternative (an enormous /// string literal) makes the whole file one token and flatters every scan that /// looks for a newline. +fn generatedPrefixLen(n: usize) usize { + return switch (n % 4) { + 0 => std.fmt.count("const value_{d}: u32 = {d}; // ", .{ n, n *% 2654435761 }), + 1 => std.fmt.count("pub fn helper_{d}(a: u32, b: u32) u32 {{ return a +% b *% {d}; }} // ", .{ n, n }), + 2 => std.fmt.count(" const text_{d} = \"lorem ipsum dolor sit amet {d}\"; // ", .{ n, n }), + else => std.fmt.count("// comment line {d} — ", .{n}), + }; +} + +fn writeGeneratedPrefix(out: *std.Io.Writer, n: usize) !void { + switch (n % 4) { + 0 => try out.print("const value_{d}: u32 = {d}; // ", .{ n, n *% 2654435761 }), + 1 => try out.print("pub fn helper_{d}(a: u32, b: u32) u32 {{ return a +% b *% {d}; }} // ", .{ n, n }), + 2 => try out.print(" const text_{d} = \"lorem ipsum dolor sit amet {d}\"; // ", .{ n, n }), + else => try out.print("// comment line {d} — ", .{n}), + } +} + fn generate(fx: Fixture) ![]u8 { - var out: std.ArrayList(u8) = .empty; - try out.ensureTotalCapacity(gpa, fx.lines * (fx.cols + 1) + 64); - var n: usize = 0; - while (n < fx.lines) : (n += 1) { - const start = out.items.len; - switch (n % 4) { - 0 => try out.print(gpa, "const value_{d}: u32 = {d}; // ", .{ n, n *% 2654435761 }), - 1 => try out.print(gpa, "pub fn helper_{d}(a: u32, b: u32) u32 {{ return a +% b *% {d}; }} // ", .{ n, n }), - 2 => try out.print(gpa, " const text_{d} = \"lorem ipsum dolor sit amet {d}\"; // ", .{ n, n }), - else => try out.print(gpa, "// comment line {d} — ", .{n}), - } - while (out.items.len - start < fx.cols) try out.append(gpa, 'x'); - try out.append(gpa, '\n'); + var byte_len: usize = 0; + for (0..fx.lines) |n| byte_len += @max(generatedPrefixLen(n), fx.cols) + 1; + const text = try gpa.alloc(u8, byte_len); + errdefer gpa.free(text); + var out: std.Io.Writer = .fixed(text); + for (0..fx.lines) |n| { + const start = out.end; + try writeGeneratedPrefix(&out, n); + const prefix_len = out.end - start; + if (prefix_len < fx.cols) try out.splatByteAll('x', fx.cols - prefix_len); + try out.writeByte('\n'); } - return out.toOwnedSlice(gpa); + std.debug.assert(out.end == text.len); + return text; } fn writeFile(path: []const u8, text: []const u8) !void { @@ -387,28 +404,30 @@ fn reportText(cells: *const [std.enums.values(Op).len][fixtures.len]Cell, bytes: /// Real stdout, not std.debug.print's stderr: this is the form `--base` reads /// back, and `zig build perf -- --json > runs/old.json` writing an empty file /// would make the next comparison silently print no ratios at all. +const json_report_max_bytes = 32 * 1024; + fn reportJson(io: std.Io, cells: *const [std.enums.values(Op).len][fixtures.len]Cell, bytes: *const [fixtures.len]usize, reps: usize) void { - var out: std.ArrayList(u8) = .empty; - defer out.deinit(gpa); - out.print(gpa, "{{\"cols\":{d},\"rows\":{d},\"reps\":{d},\"fixtures\":[", .{ screen_cols, screen_rows, reps }) catch return; + var storage: [json_report_max_bytes]u8 = undefined; + var out: std.Io.Writer = .fixed(&storage); + out.print("{{\"cols\":{d},\"rows\":{d},\"reps\":{d},\"fixtures\":[", .{ screen_cols, screen_rows, reps }) catch return; for (fixtures, 0..) |fx, fi| { - out.print(gpa, "{s}{{\"name\":\"{s}\",\"lines\":{d},\"cols\":{d},\"bytes\":{d}}}", .{ + out.print("{s}{{\"name\":\"{s}\",\"lines\":{d},\"cols\":{d},\"bytes\":{d}}}", .{ if (fi > 0) "," else "", fx.name, fx.lines, fx.cols, bytes[fi], }) catch return; } - out.appendSlice(gpa, "],\"cells\":[") catch return; + out.writeAll("],\"cells\":[") catch return; var first = true; for (std.enums.values(Op), 0..) |op, oi| { for (fixtures, 0..) |fx, fi| { const c = cells[oi][fi]; - out.print(gpa, "{s}{{\"op\":\"{s}\",\"fixture\":\"{s}\",\"min_us\":{d},\"med_us\":{d},\"p90_us\":{d},\"max_us\":{d}}}", .{ + out.print("{s}{{\"op\":\"{s}\",\"fixture\":\"{s}\",\"min_us\":{d},\"med_us\":{d},\"p90_us\":{d},\"max_us\":{d}}}", .{ if (first) "" else ",", op.label(), fx.name, c.min_us, c.med_us, c.p90_us, c.max_us, }) catch return; first = false; } } - out.appendSlice(gpa, "]}\n") catch return; - std.Io.File.stdout().writeStreamingAll(io, out.items) catch {}; + out.writeAll("]}\n") catch return; + std.Io.File.stdout().writeStreamingAll(io, out.buffered()) catch {}; } const Base = struct { med: [std.enums.values(Op).len][fixtures.len]u64 }; @@ -420,6 +439,7 @@ const Base = struct { med: [std.enums.values(Op).len][fixtures.len]u64 }; /// and did not get is worse than no comparison. fn readBase(path: []const u8) ?Base { const src = readFileAlloc(path) catch fatal("--base: cannot read {s}", .{path}); + defer gpa.free(src); var b: Base = .{ .med = @splat(@splat(0)) }; const parsed = std.json.parseFromSlice(struct { cells: []const struct { @@ -446,18 +466,22 @@ fn readFileAlloc(path: []const u8) ![]u8 { const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY }); if (fd < 0) return error.OpenFailed; defer _ = libc.close(fd); - var buf: std.ArrayList(u8) = .empty; - var chunk: [16384]u8 = undefined; - while (true) { - const n = libc.read(fd, &chunk, chunk.len); + const end = libc.lseek(fd, 0, libc.SEEK.END); + if (end < 0 or libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.StatFailed; + const size = std.math.cast(usize, end) orelse return error.FileTooLarge; + const buf = try gpa.alloc(u8, size); + errdefer gpa.free(buf); + var offset: usize = 0; + while (offset < buf.len) { + const n = libc.read(fd, buf.ptr + offset, buf.len - offset); if (n < 0) { if (libc.errno(n) == .INTR) continue; return error.ReadFailed; } - if (n == 0) break; - try buf.appendSlice(gpa, chunk[0..@intCast(n)]); + if (n == 0) return error.UnexpectedEndOfFile; + offset += @intCast(n); } - return buf.items; + return buf; } fn fatal(comptime fmt: []const u8, args: anytype) noreturn { |
