|
| 1 | +import Testing |
| 2 | +import Foundation |
| 3 | +@testable import SwiftTerm |
| 4 | + |
| 5 | +// Benchmark for PR #555 (defer buffer reflow during live-resize). |
| 6 | +// |
| 7 | +// Quantifies the cost the fix avoids. During a live-resize drag the column count |
| 8 | +// changes once per cell-width of mouse travel — each change is a real terminal.resize |
| 9 | +// → a full-scrollback reflow. This measures, at several scrollback depths: |
| 10 | +// • "drag (N reflows)" — the sum of incremental resizes across a 120→80 sweep |
| 11 | +// (today's behavior: one reflow per column step), and |
| 12 | +// • "deferred (1 reflow)" — a single 120→80 resize (the fix: reflow once, at the end). |
| 13 | +// The ratio is the wasted work the deferral removes. Direct Terminal.resize calls, so |
| 14 | +// the number is independent of the macOS view layer / which branch this runs on. |
| 15 | +@Suite("PR#555 live-resize reflow cost") |
| 16 | +struct LiveResizeReflowBenchmark { |
| 17 | + |
| 18 | + // A wrapped log-style line (~180 chars) so resizing re-wraps it across the sweep. |
| 19 | + static let line = String(repeating: "the quick brown fox jumps over the lazy dog ", count: 4) |
| 20 | + |
| 21 | + private static func filledTerminal(cols: Int, rows: Int, scrollback: Int) -> Terminal { |
| 22 | + let (terminal, _) = TerminalTestHarness.makeTerminal(cols: cols, rows: rows, scrollback: scrollback) |
| 23 | + var feed = "" |
| 24 | + for i in 0..<(scrollback + rows) { feed += "\(i): \(line)\r\n" } |
| 25 | + terminal.feed(text: feed) |
| 26 | + return terminal |
| 27 | + } |
| 28 | + |
| 29 | + private static func ms(_ body: () -> Void) -> Double { |
| 30 | + let t0 = DispatchTime.now().uptimeNanoseconds |
| 31 | + body() |
| 32 | + return Double(DispatchTime.now().uptimeNanoseconds - t0) / 1_000_000.0 |
| 33 | + } |
| 34 | + |
| 35 | + @Test func reflowCostByScrollbackDepth() { |
| 36 | + let rows = 40, startCols = 120, endCols = 80 // a 40-column drag |
| 37 | + print("\nPR#555 — live-resize reflow cost (120→80 col drag, \(rows) rows)") |
| 38 | + print("scrollback | reflows/drag | drag total ms | worst tick ms | deferred(1) ms | wasted×") |
| 39 | + print("-----------|--------------|---------------|---------------|----------------|--------") |
| 40 | + for depth in [1_000, 5_000, 10_000] { |
| 41 | + // today: one resize per column step |
| 42 | + let dragT = Self.filledTerminal(cols: startCols, rows: rows, scrollback: depth) |
| 43 | + var total = 0.0, worst = 0.0, count = 0 |
| 44 | + for c in stride(from: startCols - 1, through: endCols, by: -1) { |
| 45 | + let dt = Self.ms { dragT.resize(cols: c, rows: rows) } |
| 46 | + total += dt; worst = max(worst, dt); count += 1 |
| 47 | + } |
| 48 | + // the fix: a single resize to the final size |
| 49 | + let defT = Self.filledTerminal(cols: startCols, rows: rows, scrollback: depth) |
| 50 | + let deferred = Self.ms { defT.resize(cols: endCols, rows: rows) } |
| 51 | + print(String(format: "%10d | %12d | %13.1f | %13.2f | %14.2f | %6.1f×", |
| 52 | + depth, count, total, worst, deferred, deferred > 0 ? total / deferred : 0)) |
| 53 | + } |
| 54 | + print("") |
| 55 | + } |
| 56 | +} |
0 commit comments