⚡ Bolt: Stop rebuilding the HTML-escape replacers on every ASCII screen capture - #114
Merged
Merged
Conversation
escapeAttr and escapeText each built a fresh *strings.Replacer on every call, even though their replacement tables never change. escapeAttr runs up to a handful of times per AsciiScreenGrab call (once per non-empty attribute), and escapeText runs once per capture over the whole screen's text — both in the concurrent load-test hot path (per step, per worker). *strings.Replacer is safe for concurrent use, so the tables are hoisted to package-level vars instead. Benchmarked with a synthetic ~2000-byte screen: allocations drop from 9 to 2 per call and bytes/op from ~14952 to ~8192 (~45% less), with a ~13% wall-clock improvement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CPbfGS3iGtSU6YU2ZLtgP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 What
escapeAttrandescapeTextinconnect3270/emulator.goeach built a brand new*strings.Replaceron every single call, even though their replacement tables (&→&,<→<,>→>, and the attribute variant adding"→") are constant. Both replacers are now hoisted to package-levelvars (htmlAttrEscaper,htmlTextEscaper) built once at init.🎯 Why
escapeTextruns once per non-APIAsciiScreenGrabcall, over the entire captured screen's text — andAsciiScreenGrabis a workflow step that runs once per step, per worker, in a concurrent load test (-concurrent N).escapeAttrruns up to a handful of times per capture too (once per non-emptydata-port/data-host/data-typeattribute viacaptureAttrs).*strings.Replaceris documented as safe for concurrent use, so there's no correctness reason to rebuild it per call — it was just wasted allocation on a table that never changes, repeated on a per-workflow-step hot path.📊 Impact
Confirmed with a synthetic benchmark (~2000-byte screen, representative of an 80×24 capture):
NewReplacer)No algorithmic complexity change — this is a pure allocation/GC-pressure reduction on a per-call basis, which compounds under
-concurrentload testing where many workers hitAsciiScreenGrabrepeatedly.🔬 Measurement
Run with
go test -bench=BenchmarkEscapeText -benchmem -run=^$ ./connect3270/.Verified locally:
go vet ./...— cleango test -short ./...— all packages passgo test -run TestCompat -v ./connect3270/— all 17 compat tests pass (embedded emulator was available in this environment)No behavior change: output of
escapeAttr/escapeTextis byte-for-byte identical to before.🤖 Generated with Claude Code
https://claude.ai/code/session_015CPbfGS3iGtSU6YU2ZLtgP
Generated by Claude Code