From dce94f4453735a6f71385cc020ee79c7604609e7 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 19:50:32 -0300 Subject: Zerox opens a second pane on a file pane's buffer: one text, undo and unsaved state, a scroll and a cursor each; an edit in either is spliced into both at File.setContentSpan, O(the edit) more, and what a Save, a get, the disk watch or a rename changes on one is copied to the rest after each step Twins share the content buffer, the History and, while it is one, the line index; the other twin's cursor and scroll move past the splice as acme moves a clone's q0. Del of one twin asks nothing while another holds the text, Exit lists the text once, and a closed twin keeps a copy of its own until it is torn down. Both are in the index, each its serial, one name. An event reader on one hears no edit made through another (first cut). A dump keeps the twins as two panes on the file, not linked again. perf: edit-twin, typing beside a twin of the 50k-line file, gated within 3x of edit-char in the same run; the three baselines re-recorded for it. Co-Authored-By: Claude Opus 5.5 --- test/perf-baseline-gui-Debug.json | 2 +- test/perf-baseline-tty-Debug.json | 2 +- test/perf-baseline-tty-ReleaseFast.json | 2 +- test/perf.zig | 34 ++++++++++++++++++++++++++++++--- 4 files changed, 34 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/test/perf-baseline-gui-Debug.json b/test/perf-baseline-gui-Debug.json index ace9a967..39f5913c 100644 --- a/test/perf-baseline-gui-Debug.json +++ b/test/perf-baseline-gui-Debug.json @@ -1 +1 @@ -{"harness":"e63a8b27a0bcb261","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"gui","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":2100,"med_us":2437,"p90_us":2875,"max_us":3207},{"op":"render","fixture":"medium","min_us":82,"med_us":84,"p90_us":88,"max_us":89},{"op":"key-down","fixture":"medium","min_us":88,"med_us":95,"p90_us":102,"max_us":114},{"op":"key-right","fixture":"medium","min_us":84,"med_us":88,"p90_us":94,"max_us":105},{"op":"page-down","fixture":"medium","min_us":83,"med_us":86,"p90_us":88,"max_us":89},{"op":"wheel","fixture":"medium","min_us":82,"med_us":85,"p90_us":90,"max_us":99},{"op":"edit-char","fixture":"medium","min_us":1276,"med_us":1769,"p90_us":2239,"max_us":2754},{"op":"body-2m","fixture":"medium","min_us":5333,"med_us":9734,"p90_us":12257,"max_us":14666},{"op":"body-appends","fixture":"medium","min_us":135052,"med_us":262059,"p90_us":295829,"max_us":304576},{"op":"deep-sel","fixture":"medium","min_us":77,"med_us":79,"p90_us":83,"max_us":118}]} +{"harness":"303736f83a625e87","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"gui","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1818,"med_us":2104,"p90_us":2287,"max_us":2293},{"op":"render","fixture":"medium","min_us":78,"med_us":82,"p90_us":87,"max_us":87},{"op":"key-down","fixture":"medium","min_us":79,"med_us":81,"p90_us":89,"max_us":109},{"op":"key-right","fixture":"medium","min_us":79,"med_us":84,"p90_us":85,"max_us":89},{"op":"page-down","fixture":"medium","min_us":79,"med_us":81,"p90_us":84,"max_us":87},{"op":"wheel","fixture":"medium","min_us":77,"med_us":78,"p90_us":82,"max_us":82},{"op":"edit-char","fixture":"medium","min_us":1019,"med_us":1349,"p90_us":1597,"max_us":1632},{"op":"edit-twin","fixture":"medium","min_us":2029,"med_us":2353,"p90_us":2455,"max_us":2676},{"op":"body-2m","fixture":"medium","min_us":5978,"med_us":9708,"p90_us":11361,"max_us":11666},{"op":"body-appends","fixture":"medium","min_us":117933,"med_us":211247,"p90_us":268014,"max_us":275006},{"op":"deep-sel","fixture":"medium","min_us":73,"med_us":75,"p90_us":80,"max_us":84}]} diff --git a/test/perf-baseline-tty-Debug.json b/test/perf-baseline-tty-Debug.json index 6b5c0bd5..9110398c 100644 --- a/test/perf-baseline-tty-Debug.json +++ b/test/perf-baseline-tty-Debug.json @@ -1 +1 @@ -{"harness":"e63a8b27a0bcb261","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1830,"med_us":2222,"p90_us":2264,"max_us":2287},{"op":"render","fixture":"medium","min_us":78,"med_us":83,"p90_us":86,"max_us":94},{"op":"key-down","fixture":"medium","min_us":80,"med_us":84,"p90_us":89,"max_us":98},{"op":"key-right","fixture":"medium","min_us":78,"med_us":81,"p90_us":88,"max_us":96},{"op":"page-down","fixture":"medium","min_us":78,"med_us":82,"p90_us":89,"max_us":95},{"op":"wheel","fixture":"medium","min_us":77,"med_us":85,"p90_us":96,"max_us":109},{"op":"edit-char","fixture":"medium","min_us":1125,"med_us":1307,"p90_us":1558,"max_us":1584},{"op":"body-2m","fixture":"medium","min_us":5189,"med_us":9224,"p90_us":10859,"max_us":14741},{"op":"body-appends","fixture":"medium","min_us":108477,"med_us":198709,"p90_us":227756,"max_us":252672},{"op":"deep-sel","fixture":"medium","min_us":73,"med_us":77,"p90_us":80,"max_us":91}]} +{"harness":"303736f83a625e87","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1881,"med_us":2148,"p90_us":2324,"max_us":2644},{"op":"render","fixture":"medium","min_us":79,"med_us":87,"p90_us":90,"max_us":93},{"op":"key-down","fixture":"medium","min_us":81,"med_us":84,"p90_us":86,"max_us":90},{"op":"key-right","fixture":"medium","min_us":80,"med_us":85,"p90_us":89,"max_us":102},{"op":"page-down","fixture":"medium","min_us":85,"med_us":86,"p90_us":88,"max_us":88},{"op":"wheel","fixture":"medium","min_us":79,"med_us":85,"p90_us":90,"max_us":91},{"op":"edit-char","fixture":"medium","min_us":1037,"med_us":1300,"p90_us":1639,"max_us":1647},{"op":"edit-twin","fixture":"medium","min_us":2046,"med_us":2378,"p90_us":2529,"max_us":2585},{"op":"body-2m","fixture":"medium","min_us":5790,"med_us":9510,"p90_us":12226,"max_us":12462},{"op":"body-appends","fixture":"medium","min_us":132421,"med_us":224849,"p90_us":257368,"max_us":274890},{"op":"deep-sel","fixture":"medium","min_us":75,"med_us":80,"p90_us":84,"max_us":86}]} diff --git a/test/perf-baseline-tty-ReleaseFast.json b/test/perf-baseline-tty-ReleaseFast.json index 87a31754..4cbc8b95 100644 --- a/test/perf-baseline-tty-ReleaseFast.json +++ b/test/perf-baseline-tty-ReleaseFast.json @@ -1 +1 @@ -{"harness":"e63a8b27a0bcb261","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"ReleaseFast","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseFast","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":2042,"med_us":2280,"p90_us":2451,"max_us":2478},{"op":"render","fixture":"medium","min_us":82,"med_us":84,"p90_us":87,"max_us":104},{"op":"key-down","fixture":"medium","min_us":83,"med_us":86,"p90_us":90,"max_us":93},{"op":"key-right","fixture":"medium","min_us":84,"med_us":87,"p90_us":91,"max_us":104},{"op":"page-down","fixture":"medium","min_us":84,"med_us":87,"p90_us":93,"max_us":96},{"op":"wheel","fixture":"medium","min_us":81,"med_us":85,"p90_us":89,"max_us":89},{"op":"edit-char","fixture":"medium","min_us":1309,"med_us":1613,"p90_us":1840,"max_us":2128},{"op":"body-2m","fixture":"medium","min_us":5386,"med_us":11064,"p90_us":11706,"max_us":12374},{"op":"body-appends","fixture":"medium","min_us":144772,"med_us":232307,"p90_us":273586,"max_us":281169},{"op":"deep-sel","fixture":"medium","min_us":79,"med_us":83,"p90_us":87,"max_us":130}]} +{"harness":"303736f83a625e87","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"ReleaseFast","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseFast","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1775,"med_us":1995,"p90_us":2092,"max_us":2094},{"op":"render","fixture":"medium","min_us":80,"med_us":86,"p90_us":89,"max_us":95},{"op":"key-down","fixture":"medium","min_us":81,"med_us":83,"p90_us":95,"max_us":101},{"op":"key-right","fixture":"medium","min_us":79,"med_us":84,"p90_us":85,"max_us":98},{"op":"page-down","fixture":"medium","min_us":80,"med_us":82,"p90_us":96,"max_us":97},{"op":"wheel","fixture":"medium","min_us":78,"med_us":80,"p90_us":87,"max_us":88},{"op":"edit-char","fixture":"medium","min_us":1036,"med_us":1377,"p90_us":1602,"max_us":1621},{"op":"edit-twin","fixture":"medium","min_us":2266,"med_us":2715,"p90_us":3695,"max_us":3927},{"op":"body-2m","fixture":"medium","min_us":4739,"med_us":8675,"p90_us":11518,"max_us":13657},{"op":"body-appends","fixture":"medium","min_us":111613,"med_us":214959,"p90_us":264859,"max_us":283506},{"op":"deep-sel","fixture":"medium","min_us":74,"med_us":76,"p90_us":89,"max_us":96}]} diff --git a/test/perf.zig b/test/perf.zig index 94076a25..c9235954 100644 --- a/test/perf.zig +++ b/test/perf.zig @@ -46,6 +46,9 @@ const Op = enum { page_down, wheel, edit_char, + /// edit_char with a Zerox twin open on the same file: the twin takes + /// the splice, not a copy of the text (File.setContentSpan). + edit_twin, /// 2 MB appended to the file through 9P, on one open, in 256 KB /// writes: linear, not a copy of the whole buffer per write. body_write, @@ -67,6 +70,7 @@ const Op = enum { .page_down => "page-down", .wheel => "wheel", .edit_char => "edit-char", + .edit_twin => "edit-twin", .body_write => "body-2m", .body_appends => "body-appends", .deep_selection => "deep-sel", @@ -511,6 +515,19 @@ fn gate(cells: *const [std.enums.values(Op).len][fixtures.len]Cell, base: *const failed = true; } }; + // Typing beside a twin costs about what typing alone does, whatever the + // baseline: the twin takes the splice, never a copy of the text. + const twin_op = @intFromEnum(Op.edit_twin); + const alone_op = @intFromEnum(Op.edit_char); + for (fixtures, 0..) |fx, fi| { + const twin = cells[twin_op][fi].min_us; + const alone = cells[alone_op][fi].min_us; + if (twin == 0 or alone == 0) continue; + if (@as(f64, @floatFromInt(twin)) > ratio * @as(f64, @floatFromInt(alone))) { + std.debug.print("perf gate: edit-twin on {s} took {d} us at best, over {d:.1}x edit-char's {d} us\n", .{ fx.name, twin, ratio, alone }); + failed = true; + } + } if (failed) return error.PerformanceRegression; std.debug.print("perf gate: every cell within {d:.1}x of the baseline\n", .{ratio}); } @@ -545,7 +562,13 @@ fn measure(op: Op, fx: Fixture, path: []const u8, expected_bytes: usize, reps: u core.active = id; const pane = core.panes[id].?; _ = try frame(core); - if (op == .edit_char) { + if (op == .edit_twin) { + if (!core.executeBuiltinLine(id, "Zerox")) return error.ZeroxRefused; + pump(core); + core.active = id; + _ = try frame(core); + } + if (op == .edit_char or op == .edit_twin) { core.update(.{ .key = .{ .cp = 'i', .text = "i" } }); pump(core); } @@ -572,7 +595,7 @@ fn measure(op: Op, fx: Fixture, path: []const u8, expected_bytes: usize, reps: u .key_right => core.update(.{ .key = .{ .cp = 'l', .text = "l" } }), .page_down => core.update(.{ .key = .{ .cp = pardes.Key.page_down } }), .wheel => core.update(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = wheel_col, .row = wheel_row } }), - .edit_char => core.update(.{ .key = .{ .cp = 'x', .text = "x" } }), + .edit_char, .edit_twin => core.update(.{ .key = .{ .cp = 'x', .text = "x" } }), .body_write => bodyWrite(core, pane.serial), .body_appends => bodyAppends(core, pane.serial), } @@ -591,11 +614,16 @@ fn measure(op: Op, fx: Fixture, path: []const u8, expected_bytes: usize, reps: u return error.PageDidNotScroll, .wheel => if (pane.scroll() <= before.scroll and before.scroll < @as(i32, @intCast(fx.lines)) - pane.rows) return error.WheelDidNotScroll, - .edit_char => { + .edit_char, .edit_twin => { if (pane.file.?.content.len != before.len + 1 or pane.body.cur_row != before.row or pane.body.cur_col != before.col + 1) return error.CharacterWasNotInserted; const line = pardes.panes.File.sourceLine(pane, before.row); if (line[@intCast(before.col)] != 'x') return error.CharacterWasNotInserted; + if (op == .edit_twin) for (core.panes, 0..) |slot, other| { + const twin = slot orelse continue; + if (other == id or twin.file == null or twin.file.?.twin != pane.file.?.twin) continue; + if (twin.file.?.content.ptr != pane.file.?.content.ptr) return error.TwinMissedTheEdit; + } else if (pane.file.?.twin == 0) return error.NoTwin; }, } if (n >= warmup) samples[n - warmup] = dt / 1000; -- cgit v1.3