[perf-improver] perf: eliminate string allocations in ANSI render hot path#8769
Merged
Evangelink merged 1 commit intoJun 2, 2026
Merged
Conversation
Add CsiEraseInLine and CsiEraseInDisplay constants to AnsiCodes, replacing runtime string interpolations that ran every 500ms render cycle. Optimize MoveCursorUp to write directly to StringBuilder in batching mode, avoiding a per-call string allocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR optimizes the Microsoft.Testing.Platform ANSI terminal live-progress rendering path by removing avoidable short-lived string allocations in frequently executed ANSI escape sequence writes.
Changes:
- Introduced
AnsiCodes.CsiEraseInLineandAnsiCodes.CsiEraseInDisplayconstants to avoid runtime string construction for common CSI+erase sequences. - Replaced repeated interpolations in
AnsiTerminalTestProgressFrame.RenderandAnsiTerminal.EraseProgresswith the new constants. - Updated
AnsiTerminal.MoveCursorUpto append cursor-movement sequences directly to the batchingStringBuilder(avoiding intermediate strings).
Show a summary per file
| File | Description |
|---|---|
| src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiTerminalTestProgressFrame.cs | Uses new precomputed CSI erase constants in the render hot path. |
| src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiTerminal.cs | Avoids intermediate string allocation when batching cursor moves; uses CSI erase constant for progress erase. |
| src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiCodes.cs | Adds const string shortcuts for CSI+erase sequences to enable allocation-free reuse. |
Copilot's findings
- Files reviewed: 3/3 changed files
- Comments generated: 0
JanKrivanek
approved these changes
Jun 2, 2026
2 tasks
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.
🤖 This is an automated contribution from Perf Improver.
Goal and Rationale
The live-progress render loop runs every 500 ms while tests are executing. Three allocation sites in this path were building temporary strings from compile-time-known constants:
$"{AnsiCodes.CSI}{AnsiCodes.EraseInLine}"— used 4× inAnsiTerminalTestProgressFrame.Render(once per changed progress or detail line). The result is always the literal"\x1b[K".$"{AnsiCodes.CSI}{AnsiCodes.EraseInDisplay}"— used inAnsiTerminalTestProgressFrame.RenderandAnsiTerminal.EraseProgress. Always"\x1b[J".MoveCursorUpstring allocation —AnsiTerminal.MoveCursorUpbuilt a temporary string even when batching mode was active and the result went straight into aStringBuilder.Approach
AnsiCodes.CsiEraseInLine(= CSI + EraseInLine) andAnsiCodes.CsiEraseInDisplay(= CSI + EraseInDisplay) asconst stringfields. The C# compiler evaluates these at compile time; no runtime allocation.$"{AnsiCodes.CSI}{AnsiCodes.EraseIn*}"interpolations with the new constants.AnsiTerminal.MoveCursorUp, for the hot batching path, replaced string interpolation with directStringBuilder.Appendchaining (Append(CSI).Append(lineCount).Append(MoveUpToLineStart).AppendLine()). The non-batching path retains the interpolation since it only runs on the very first call after progress is erased.Performance Evidence
CsiEraseInLine(per changed line)stringallocation ~5 bytesCsiEraseInDisplay(erase / render)stringallocation ~4 bytesMoveCursorUpin batching pathstringallocation ~8 bytesStringBuilderappendMethodology: code inspection + diff review. The new constants are
const stringfields initialised from otherconst stringfields; the C# compiler folds them at compile time.Trade-offs
AnsiCodesgains two constants (+12 lines). Naming follows the existingSetDefaultColorpattern (a shortcut that combines prefix + suffix into one constant).MoveCursorUpstill builds a string — it's invoked only once per erase cycle (not per frame), so the impact is negligible.Test Status
Microsoft.Testing.Platform.UnitTests(net8.0): 1003 passed, 0 failed, 3 skipped ✅Reproducibility
Add this agentic workflows to your repo
To install this agentic workflow, run