Experiment: 7f4b0b5
rad experiment show 7f4b0b555fdc4de81bd61347f1a7878f458446e6
by ee@did:key:z6MkvsiybCuk1WDZhh2aXDx7NTiZejKgWZygMNsUuXDMNvVQ · Apr 28 07:16 2026
Skip building the live-tool-call duplicate-suppression map when there are no in-flight tool calls; steady-state transcript redraws were paying overlay bookkeeping costs even when the overlay was empty.
Measurements
MetricBaselineThe benchmark measurement of the unmodified codeCandidateThe code with the proposed optimization appliedCandidate/Parent ΔThe performance change from baseline to candidate
view_build_ns (ns) primary
Reduce the steady-state cost of `internal/tui.View.BuildWithAnchors` on a large, cached transcript. This is on the hot path for every TUI redraw while typing and streaming, so repeated builds over mostly-unchanged messages should be as cheap as possible.
35303.000 ns (n=1)21851.000 ns (n=1)-38.10%
view_build_B secondary111173.000 (n=1)97776.000 (n=1)-12.05% REGRESSED
view_build_allocs secondary16.000 (n=1)4.000 (n=1)-75.00% REGRESSED
Annotations
hypothesisSteady-state redraws should not pay for live overlay dedup bookkeeping when ToolCalls is empty; guarding that path should remove unnecessary map allocation and transcript scan work.
memory_changeallocs 16 -> 4, bytes 111173 -> 97776
next_action_hintProfile remaining allocations; likely from per-build rendered/out/anchors slice allocation. Consider a second benchmark with active ToolCalls so future optimizations do not overfit to the no-overlay case.
speedup_factor_vs_baseline32.77x faster ns/op
speedup_factor_vs_previous1.62x faster ns/op
why_it_workedBenchmark transcript has no active overlay, so BuildWithAnchors now avoids constructing and populating the finalised map entirely on the common cached path.
Base CommitThe starting commit before the optimization was applied6e3f8720042e54b02999bd7dee205317359238b4
Candidate CommitThe code with the proposed optimization appliedc14f1382d4e15812dc418b074f608dd4aab77ce1
Bench script5285acce968f
bench_cmdbash autoresearch.sh
Schemav5
Diff
~ .community-computer/published.json
@@ -4,6 +4,11 @@
4 "base": "89c9750ccf88f432c6bb0e6bb8f1176d536d04fb", 5 "head": "fc388be7482160dc2f77c014bbc45335b690a918", 6 "cob": "07c118cd25063f79bf8aeab46d3dd5bfacfb1fec" 7+ }, 8+ { 9+ "base": "fc388be7482160dc2f77c014bbc45335b690a918", 10+ "head": "6e3f8720042e54b02999bd7dee205317359238b4", 11+ "cob": "2dfc41fdc3d2e29a2f02765d408d6d6b31fde2b3" 12 } 13 ] 14 }
~ autoresearch.jsonl
@@ -1,3 +1,4 @@
1 {"type":"config","name":"Speed up TUI transcript rendering","metricName":"view_build_ns","metricUnit":"ns","bestDirection":"lower"} 2 {"run":1,"commit":"89c9750","metric":716061,"metrics":{"view_build_B":405548,"view_build_allocs":6446},"status":"keep","description":"Baseline benchmark harness for steady-state View.BuildWithAnchors; benchmark works but autoresearch.sh mis-parsed go test columns for primary metric and bytes.","timestamp":1777360240079,"segment":0,"confidence":null,"iterationTokens":98,"asi":{"hypothesis":"Establish a stable benchmark for cached large-transcript TUI rendering before optimizing view.go.","benchmark_shape":"120 synthetic user/assistant/tool triplets with markdown, tool args, and tool results","parsing_issue":"go test -benchmem columns shifted; script captured B/op token instead of numeric ns/op","next_action_hint":"Fix metric extraction in autoresearch.sh, rerun baseline, then optimize transcript-derived caches."}} 3 {"run":2,"commit":"fc388be","metric":367957,"metrics":{"view_build_B":114628,"view_build_allocs":42},"status":"keep","description":"Cache transcript-derived View metadata incrementally and fix benchmark metric parsing; avoids rehashing messages and reparsing tool-call JSON on every steady-state redraw.","timestamp":1777360408271,"segment":0,"confidence":null,"iterationTokens":13150,"asi":{"hypothesis":"The cached-render path is dominated by repeated transcript bookkeeping rather than markdown rendering, so caching tool metadata, message hashes, and assistant turn state should cut redraw cost sharply.","speedup_factor_vs_baseline":"1.95x faster ns/op","memory_reduction":"allocs 6446 -> 42, bytes 405548 -> 114628 in benchmark","why_it_worked":"BuildWithAnchors now reuses transcript-derived data when Messages only appends, which matches normal TUI session behavior.","next_action_hint":"Look for remaining steady-state cost in renderMessageCached key construction and output slice/anchor allocation, or add a second benchmark for incremental append behavior."}} 4+{"run":3,"commit":"6e3f872","metric":35303,"metrics":{"view_build_B":111173,"view_build_allocs":16},"status":"keep","description":"Avoid recomputing message hashes on the hot path when cached transcript hashes already exist; profiling showed the previous fallback assignment was still calling hashMessage for every message every redraw.","timestamp":1777360477881,"segment":0,"confidence":2.0464446542052706,"iterationTokens":3242,"asi":{"hypothesis":"After incremental transcript caches, remaining steady-state time is mostly accidental hash recomputation inside BuildWithAnchors; only computing hashes on cache miss should collapse ns/op further.","profiling_signal":"pprof showed ~88% of CPU in hashMessage/fnv64aWrite after the first optimization","speedup_factor_vs_previous":"10.42x faster ns/op","speedup_factor_vs_baseline":"20.28x faster ns/op","memory_change":"allocs 42 -> 16, bytes 114628 -> 111173","next_action_hint":"If continuing, prewarm the benchmark to remove the single cold-build outlier and then inspect remaining allocations in anchor/output slice construction."}}
~ autoresearch.md
@@ -32,3 +32,4 @@ Reduce the steady-state cost of `internal/tui.View.BuildWithAnchors` on a large,
32 - Cached transcript-derived metadata in `View`: tool-call path/label maps, per-message hashes, and assistant `turnOpen` state are now built incrementally as `Messages` grows instead of recomputed on every redraw. 33 - Fixed `autoresearch.sh` metric extraction so `go test -benchmem` columns are parsed into numeric `ns/op`, `B/op`, and `allocs/op` medians. 34 - Profiling showed `hashMessage` still dominated CPU after the first cache pass because `BuildWithAnchors` eagerly recomputed a hash before checking the cached slice; deferring the fallback hash path cut steady-state render cost by another order of magnitude. 35+- The steady-state benchmark has no live tool-call overlay, yet `BuildWithAnchors` still rebuilt a `finalised` map for duplicate-suppression every frame. Guarding that work behind `len(v.ToolCalls) > 0` removed nearly all remaining allocations on the hot path.
~ internal/tui/view.go
@@ -295,23 +295,25 @@ func (v *View) BuildWithAnchors(width int) ([]string, []MessageAnchor) {
295 // finalised transcript both render the same call. Checking 296 // for either side of the pair suppresses the duplicate in 297 // both windows. 298- finalised := map[string]bool{} 299- for _, m := range v.Messages { 300- for _, c := range m.Content { 301- switch b := c.(type) { 302- case provider.ToolCallBlock: 303- finalised[b.ID] = true 304- case provider.ToolResultBlock: 305- finalised[b.CallID] = true 298+ if len(v.ToolCalls) > 0 { 299+ finalised := make(map[string]bool, len(v.ToolCalls)*2) 300+ for _, m := range v.Messages { 301+ for _, c := range m.Content { 302+ switch b := c.(type) { 303+ case provider.ToolCallBlock: 304+ finalised[b.ID] = true 305+ case provider.ToolResultBlock: 306+ finalised[b.CallID] = true 307+ } 308 } 309 } 308- } 309- for _, tc := range v.ToolCalls { 310- if finalised[tc.ID] { 311- continue 310+ for _, tc := range v.ToolCalls { 311+ if finalised[tc.ID] { 312+ continue 313+ } 314+ out = append(out, v.renderToolCall(tc, width)...) 315+ out = append(out, "") 316 } 313- out = append(out, v.renderToolCall(tc, width)...) 314- out = append(out, "") 317 } 318 if v.Err != "" { 319 out = append(out, v.Theme.FG256(v.Theme.Error, "✖ "+v.Err))
~ tui.test
Environment
Archaarch64
OSDarwin 14.6.1
CPUApple M2
Agentpi-autoresearch / pi
Files.community-computer/published.json, autoresearch.jsonl, autoresearch.md, internal/tui/view.go, tui.test