Skip to content

[Performance] 极限设备性能宪章与长期预算 #12

Closed

Description

@m1ngsama

Objective

Keep TUT responsive and resource-bounded on old, low-power, low-memory systems. This is a long-lived performance contract, not a one-time benchmark exercise and not a reason to trade away correctness, portability, privacy, or maintainability.

Direction and evidence remain outside repository documentation: production invariants, deterministic tests, typed bounded logs, CI, release artifacts, and this issue carry the contract.

Enforced baseline

  • Source input is capped at 32 MiB; path reads remain positional and stdin is copied into an immutable private snapshot.
  • Terminal geometry, ratatui double buffers, visible render storage, line/search checkpoints, highlight targets, match blocks, row neighborhoods, and queries all have explicit hard limits.
  • Line indexing, search, viewport location, and visible rendering advance incrementally; projected-row work yields at grapheme emission boundaries.
  • Repeated line lookup, navigation, highlighting, rendering, and source-window work have deterministic counters that prevent replay and allocation regressions.
  • Default execution remains quiet: no telemetry, network access, worker threads, idle work, or timing overhead.
  • Optional typed logs contain aggregate counters and durations only; they never contain paths, names, text, queries, keys, or terminal contents.

Target environment

ThinkPad X230-class dual-core CPU, 2 GiB available memory, ordinary SSD, and 80x24 terminal. Inputs are 1, 8, and 32 MiB UTF-8 text with ASCII, CJK, emoji, long-line, no-newline, dense-match, and pathological-grapheme cases.

Long-term budgets

Metric Target Regression line
1 MiB open-to-first-frame <= 30 ms > 100 ms
32 MiB open-to-first-frame <= 500 ms > 1 s
Peak RSS <= input x 1.5 + 16 MiB > input x 2 + 32 MiB
Scroll/page p99 <= 16.7 ms > 33 ms
First search result at 32 MiB <= 100 ms > 300 ms
Idle CPU approximately 0% > 0.5%
Release binary <= 1 MiB > 2 MiB

Wall-clock values are hardware observations, not portable correctness assertions. CI should fail on deterministic byte, window, allocation, capacity, step, and output bounds; timing results require recorded environment and repeated samples.

Measurement contract

  • Record commit, toolchain, OS, hardware, terminal geometry, workload, sample count, and p50/p95/p99.
  • Use at least five warm samples; report cold/cache-miss results separately when relevant.
  • Separate ingest, UTF-8/grapheme segmentation, line indexing, layout, rendering, terminal draw, navigation, and search.
  • Measure delivered bytes, source windows, segmentation progress, allocation attempts/capacities, background steps, frame work, binary size, and peak RSS before drawing timing conclusions.
  • Exercise end-to-end latency through a real PTY as well as pure algorithm fixtures.
  • Reject performance claims based only on the best run, OS page cache, or a microbenchmark that bypasses the TUI boundary.

Acceptance

  • Provide a reproducible code/test command for the fixed workload matrix without production credentials or network access.
  • Emit a machine-readable bounded result suitable for comparison without adding free-form production logging.
  • Gate deterministic resource and replay bounds in CI; publish timing/RSS baselines as issue or release records, not repository docs.
  • Demonstrate stable degradation or typed refusal at the 32 MiB and terminal/render limits without swap storms or aborts.
  • Prove resize storms, continuous navigation, repeated searches, suspend/resume, and error retries do not cause cumulative cache growth.
  • Track unexplained regression-line breaches with a focused issue and recovery plan before release.

Scope

Each change must close a measured or provable gap in an independently reviewable slice. This charter does not authorize feature expansion, speculative abstraction, wall-clock-only CI, repository documentation, benchmark theater, or broad rewrites without evidence.

Activity

  1. m1ngsama commented on Aug 10, 2026

    @m1ngsama
    OwnerAuthor

    Post-v0.0.4 design note: the next independently reviewable performance slice can satisfy the first three acceptance items without adding production telemetry or wall-clock CI.

    Proposed command and output contract

    make bounded-results | tee target/tut-bounds-v1.ndjson

    The command would run only cfg(test) instrumentation and emit a fixed tut-bounds/v1 NDJSON stream:

    • exactly 16 newline-terminated records;
    • at most 1,024 ASCII bytes per record and 16 KiB total;
    • closed-enum case/kind strings plus checked decimal u64 counters;
    • no paths, text, queries, keys, terminal contents, timings, RSS, or free-form messages;
    • one schema record, 12 core workloads, one limits record, one recorder record, and one real-PTY contract record.

    CI should assert algorithmic ceilings and schema/output bounds, while retaining measured totals for comparison. It should not freeze today's exact totals as a golden file that prevents later optimizations.

    Fixed workload set

    All core cases use an 80x24 terminal and execute load → first complete frame → G → fixed search to completion → n → stable frame.

    • 1 MiB: ASCII/LF, CJK/LF, emoji+ZWJ+combining, dense x\0.
    • 8 MiB: ASCII/CRLF, mixed Unicode, 64 KiB physical lines, no newline.
    • 32 MiB: ASCII/LF, dense x\0, one long no-newline line, and 32 exact 1 MiB EGCs.

    Inputs should be generated into one temporary file in fixed 64 KiB chunks and removed between cases, so the harness never retains the whole matrix in memory. Separate records should prove atomic refusal at source+1, terminal geometry, and visible-render limits; FakeDriver/TraceRecorder should prove one bounded operation per loop; the existing PTY fixture should prove cleanup/termios/typed-log shape without recording elapsed time.

    Deterministic gates

    The runner should retain the existing hard contracts rather than introduce timing thresholds:

    • at most one source window per line/search phase;
    • projected rendering at most 1,024 grapheme emissions per turn, including the existing bounded oversized-EGC overshoot;
    • highlight merge at most 1,024 comparisons/output attempts per phase and grow at most one fallible reserve;
    • no partial frame or partial highlight publication;
    • render storage remains within 32 MiB, highlight targets within 4 MiB, and all limit failures remain typed and atomic;
    • resize/navigation/search/retry sequences do not accumulate capacity after a warm high-water mark.

    The repository already has the required DocumentMetrics, render/search reserve counters, RuntimeRecorder, FakeDriver/TraceRecorder, and real PTY fixtures. The implementation should therefore be limited to a test-only workload generator/aggregator, a fixed no-dependency NDJSON formatter, minimal cfg(test) getters, a Make target, and one stable-Ubuntu CI invocation. Rust 1.88 and macOS should continue compiling this code through the existing gates without redundantly running the large matrix.

    This is a design record only; no production API, dependency, repository documentation, credentials, network step, or timing gate has been added yet.

  2. m1ngsama commented on Aug 11, 2026

    @m1ngsama
    OwnerAuthor

    PR #105 merged as 191bdec510fc70010a16e43ca0dc98ddc1fecc3b and completes the first three acceptance items.

    Evidence:

    • make bounded-results emits exactly 16 ASCII NDJSON records (8,418 bytes total; largest record 626 bytes) for the fixed 1/8/32 MiB matrix.
    • Stable Ubuntu CI runs the release/overflow matrix and only publishes the result file after a successful command; failure injection exits nonzero with no stdout publication.
    • Current, Rust 1.88, macOS, real PTY, strict Clippy, overflow, packaging, and post-merge main CI all passed (run 31473599456).
    • The stream contains only closed case/kind identifiers and checked counters; PTY coverage asserts paths, filenames, text, and query values do not appear.

    The issue intentionally remains open. A separately reviewable follow-up will close the remaining storm/retry capacity proof and record the existing regression-recovery evidence before final closure.

  3. m1ngsama commented on Aug 11, 2026

    @m1ngsama
    OwnerAuthor

    Correction to the preceding status note: PR #105 fully completes acceptance items 1 and 2 and installs the deterministic CI gate portion of item 3. Item 3 remains unchecked until a real, environment-recorded timing/RSS baseline (including at least five warm samples and separate cold observations) is published here or in release records. No timing/RSS values are inferred from CI counters.

  4. m1ngsama commented on Aug 11, 2026

    @m1ngsama
    OwnerAuthor

    Final external PTY baseline — audited

    Final evidence was recorded from clean main == origin/main at commit 355ce364cf8ecce80be69192c68fe075a7d0f994.

    Evidence identity:

    • Result SHA-256: cdcadd3645002ea7c477584fdf466a8dd6a0a5c98284449ad129ddd1ee0a8ce5
    • PTY harness SHA-256: ffe6ab4e05f7f3e04c4068abdd02d04272c49e531428debb9e148b18219b645a
    • Workload generator SHA-256: 1860fb04f630fc0e6817c718409d959c28b4b1e4f9c0b1a2015ff8b671036077
    • Staged stripped binary: 787,936 bytes; SHA-256 033b5f18af3117b17f9d1b5b2a65da01be80dc7e9254fd7f794a659959919a2b
    • Closed 1 MiB workload SHA-256: bd92cbb8b6a5e720718d9d87b3a68001145d2720168ffe43d0a33f5076a1948b
    • Closed 32 MiB workload SHA-256: cabe76120222f040ba19de1c0501e10518f638166b412614e94ffdf47cd0eb2d

    Environment: Apple M4, 10 cores, 24 GiB memory, arm64, macOS 15.7.3 build 24G419, Rust 1.97.1, LLVM 22.1.6, fixed 80×24 PTY, TERM=xterm-256color, LC_ALL=C.

    Sampling used nearest-rank percentiles:

    • 5 independent new-process/no-preread samples per workload
    • 100 warm samples per workload, with a full preread immediately before every process
    • 5 warm idle samples per workload, each with a 30-second idle window
    • 220 total samples; failures and timeouts: 0

    Latency aggregates

    Values are p50/p95/p99 in milliseconds.

    Workload Class (n) Open→frame Line down Page down First search
    1 MiB no-preread (5) 5.824458/8.080125/8.080125 0.382458/0.606833/0.606833 0.391833/0.505375/0.505375 0.856708/1.095458/1.095458
    1 MiB warm (100) 3.895791/9.093792/11.178000 0.245708/0.550792/0.624125 0.249334/0.515458/0.568375 0.567792/1.140792/1.321500
    1 MiB idle (5) 3.797958/4.736791/4.736791 0.259459/0.300208/0.300208 0.237041/0.314458/0.314458 0.525500/0.678875/0.678875
    32 MiB no-preread (5) 3.708500/4.764042/4.764042 0.250584/0.356375/0.356375 0.246542/0.326667/0.326667 5.373875/5.447750/5.447750
    32 MiB warm (100) 4.280000/7.577708/9.367875 0.241625/0.375708/0.542416 0.243750/0.407416/0.537125 5.419292/6.247875/6.916250
    32 MiB idle (5) 4.936417/5.958000/5.958000 0.316125/0.337458/0.337458 0.302542/0.349000/0.349000 5.608000/5.840791/5.840791

    Resource aggregates

    RSS values are p50/p95/p99/maximum in MiB. Idle CPU values are p50/p95/p99 percentages.

    Workload Class (n) Peak RSS Idle CPU
    1 MiB no-preread (5) 3.062500/3.062500/3.062500/3.062500 —
    1 MiB warm (100) 3.062500/3.421875/3.562500/3.734375 —
    1 MiB idle (5) 3.218750/3.234375/3.234375/3.234375 0.000000/0.000000/0.000000
    32 MiB no-preread (5) 6.515625/6.703125/6.703125/6.703125 —
    32 MiB warm (100) 6.531250/6.921875/7.125000/7.125000 —
    32 MiB idle (5) 6.437500/6.531250/6.531250/6.531250 0.033328/0.033332/0.033332

    The idle CPU clock has 10 ms resolution. A zero result therefore means below a 0.033333% upper bound over the 30-second window, not mathematically zero CPU use.

    Budget evaluation

    The conservative value uses the worst class p99 for latency, maximum observed RSS, and worst idle p99.

    Regression metric Conservative observation Target Regression line Verdict
    1 MiB open→frame 11.178000 ms ≤30 ms >100 ms target passed
    32 MiB open→frame 9.367875 ms ≤500 ms >1 s target passed
    Line-down p99 0.624125 ms ≤16.7 ms >33 ms target passed
    Page-down p99 0.568375 ms ≤16.7 ms >33 ms target passed
    First 32 MiB search 6.916250 ms ≤100 ms >300 ms target passed
    1 MiB peak RSS 3.734375 MiB ≤17.5 MiB >34 MiB target passed
    32 MiB peak RSS 7.125000 MiB ≤64 MiB >96 MiB target passed
    Idle CPU 0.033332% approximately 0% >0.5% no breach
    Release binary 787,936 B (0.751434 MiB) ≤1 MiB >2 MiB target passed

    All 220 samples exited successfully with empty stderr, restored termios, valid terminal lifecycle ordering, exactly one alternate-screen enter/leave, required cursor cleanup, and zero swap operations.

    The macOS wait4 and /usr/bin/time -l RSS channels both reported 2,981,888 bytes for the cross-check scenario; the latter also reported a 2,261,280-byte peak memory footprint and zero swaps.

    Limits of this evidence

    • These wall-clock observations are from Apple M4 hardware, not an X230-class system; they are environment-scoped and not portable timing assertions.
    • “No-preread” means the harness performed no preread before launching a new process. The macOS unified buffer cache was uncontrolled, so this is not presented as a true disk-cold/cache-miss result.
    • Open→frame measures immediately before process fork through the first externally observed reader frame; it is not an open(2) duration.
    • The external timing corpus is closed ASCII/LF text at 1 and 32 MiB. It complements deterministic CI coverage and does not replace it.
    • Wall-clock/RSS values remain issue evidence, not CI timing gates.

    No measurement gate failed and no regression-line breach was observed. This run therefore does not trigger a focused recovery issue.

  5. m1ngsama commented on Aug 11, 2026

    @m1ngsama
    OwnerAuthor

    Deterministic resilience and governance closure

    PR #107 merged as 355ce364cf8ecce80be69192c68fe075a7d0f994; its pull-request CI and post-merge main CI both passed on current Ubuntu, Rust 1.88 Ubuntu, and current macOS.

    The test-only capacity proof now accounts component-by-component for line checkpoints, both document windows, row-neighborhood edges, both active locator queues, stale or scanning render storage, the full search session, and active/help-return search drafts. It samples after every action, background phase, and draw. One maximum warm round is followed by eight verification rounds whose every component must remain at or below its warm high-water mark.

    The fixed resilience matrix covers:

    • a rapid usable/tiny/maximum/same-size resize storm with at most one background phase between actions;
    • continuous line, page, end, and start navigation under the same one-phase rule;
    • repeated searches, match navigation, and help round trips with a retained draft;
    • eight suspend/resume cycles per session with exact terminal restoration counts;
    • allocation failure and terminal poll-error retries followed by successful recovery.

    Each sequence also checks its functional end state. Transition tests separately force nonzero scanning render storage, stale cached render storage, both locator queues, and help-return draft storage so the accounting cannot pass through unreachable fields.

    Acceptance item 4 is supported by the deterministic source, terminal-geometry, visible-render, and frame-symbol boundary/refusal tests from #105 and #104, plus the final 220-session PTY baseline above: all limit failures are typed and atomic, retries succeed, and observed swap operations and aborts were zero. Acceptance item 5 is covered by #107. No production telemetry, timing gate, background work, or public API was added.

    Regression-governance audit

    The historical record is mixed and is recorded without retroactive polishing:

    Case Detection and release boundary Recovery Verdict
    #39 Opened 2026-08-10T16:23:24Z with the approximately 157 ms pathological-frame observation and a recovery plan. v0.0.3 shipped only 26m11s later and did not disclose it. #49, #50, #51, and #52 made layout/render work resumable and bounded; the final fix merged at 18:13:49Z, before v0.0.4. It meets the literal focused-issue-and-plan-before-release wording, but the 26-minute undisclosed window is a historical process deviation under the stricter rule below. No post-fix wall-clock value is invented.
    #87 Opened 2026-08-10T23:04:29Z; the fixture demonstrated a 115,277,824-byte combined request and at least a 48,168,960-byte under-count relative to the named 64 MiB budget. v0.0.5 shipped 8h15m later and explicitly disclosed #87. #104 merged at 2026-08-11T07:55:08Z, 35m33s after that release and before any later release. The 14-day lead time was not met, but the release-notes disclosure fallback was met and the gap was fixed before the next release.

    Prospective rule recorded by this closure:

    1. Record every known regression-line breach in a focused issue with reproducible evidence and a recovery plan at least 14 days before release.
    2. If discovered inside that window, delay the release or explicitly disclose its impact, tracking issue, and recovery target in release notes.
    3. Do not silently release a known breach. Close recovery only after deterministic verification and CI; publish a post-fix measurement when the original claim was wall-clock based.

    At this snapshot, there is no recorded untracked or unexplained budget breach. That is a governance statement, not proof that every future workload on every machine meets every wall-clock target. The audited external baseline found no new regression-line breach, so no additional recovery issue is required before closing this charter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions