Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce string allocations in IConsoleOutput implementations #3978

Open
wants to merge 11 commits into
base: v2_develop
Choose a base branch
from

Conversation

TheTonttu
Copy link
Contributor

@TheTonttu TheTonttu commented Mar 9, 2025

Fixes

Proposed Changes

  • Change IConsoleOutput.Write(string) to IConsoleOutput.Write(ReadOnlySpan<char>).
    • More flexible buffer options for caller.
  • NetOutput allocation optimizations.
    • Avoid Rune.ToString()
    • Console.Write(object) -> StringBuilder.ToString() vs
      Console.Out.Write(StringBuilder) -> TextWriter.Write(StringBuilder).
  • WindowsOutput allocation optimizations.
    • Change WriteConsole Interop to take ReadOnlySpan<char> instead of string.
      • Note that entry point name requires the A or W suffix because LibraryImport does not choose it automatically.
    • Copy to rented array instead of StringBuilder.ToString() during write.
    • Remove unnecessary StringBuilder from SetCursorVisibility.
  • Change WindowsConsole WriteConsole Interop to take ReadOnlySpan<char> instead of string.
    • For verifying that this alone does not cause regressions.
  • Use EscSeqUtils.CSI_Append(Foreground|Background)ColorRGB in IConsoleOutput implementations.
  • Add EscSeqUtils void CSI_WriteCursorPosition(TextWriter, int, int) alternative to string CSI_SetCursorPosition(int, int).
  • Change EscSeqUtils.CSI_(Enable|Disable)MouseEvents static properties to readonly fields
    • Consistent with the other similar fields.
    • Cannot accidentally reassign the values on runtime.

Pull Request Checklist

  • I've named my PR in the form of "Fixes #issue. Terse description."
  • My code follows the style guidelines of Terminal.Gui - if you use Visual Studio, hit CTRL-K-D to automatically reformat your files before committing.
  • My code follows the Terminal.Gui library design guidelines
  • I ran dotnet test before commit
  • I have made corresponding changes to the API documentation (using /// style comments)
  • My changes generate no new warnings
  • I have checked my code and corrected any poor grammar or misspellings
  • I conducted basic QA to assure all features are working

UI Catalog benchmark

No noticeable change to times. Both v2win and v2net finished in ~03:48. I'm guessing the v2 application or drivers have input throttling/pacing which kind of makes the benchmark timings moot for them.

For comparison v1 Windows driver finished in ~00:35.

Microbenchmarks

CSI_SetCursorPosition vs CSI_WriteCursorPosition


BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5487/22H2/2022Update)
AMD Ryzen 7 5800X3D, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.406
  [Host]     : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

writer=Syste(...)riter [35]  

Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
Set 47.04 ns 1.278 ns 3.769 ns 44.46 ns 1.01 0.11 0.0008 40 B 1.00
Write 27.60 ns 0.084 ns 0.065 ns 27.59 ns 0.59 0.04 - - 0.00

Allocations

v2net

Reduced string allocations nicely.

Before After
v2net-01 object-allocations strings-before v2net-02 object-allocations strings-after

v2win

Not that much improvement. Eliminated SetCursorVisibility char array and StringBuilder allocations.

Before After
v2win-01 object-allocations strings-before v2win-02 object-allocations strings-after
v2win-01 object-allocations drilldown-string-ctor-ros-before v2win-02 object-allocations drilldown-string-ctor-ros-after
v2win-01 object-allocations drilldown-stringbuilder-before v2win-02 object-allocations drilldown-stringbuilder-after
v2win-01 object-allocations array-of-chars-before v2win-02 object-allocations array-of-chars-after
v2win-01 object-allocations drilldown-array-of-chars-arraypool-before v2win-02 object-allocations drilldown-array-of-chars-arraypool-after

TheTonttu added 10 commits March 9, 2025 22:25
…n<char>

Allows the caller more flexibility about choosing a buffer per use case.
Writes cursor position sequence to text writer without string allocation.
…ly fields

Changed for the sake of consistency with rest of the EscSegutils fields rather than performance. Also prevents bugs from accidentally setting the properties.
…buffer

The large intermediate string builder remains a challenge. :)
Also might have missed one of the Console.Out.Write(StringBuilder) calls...
@TheTonttu TheTonttu requested a review from tig as a code owner March 9, 2025 22:44
@tznind
Copy link
Collaborator

tznind commented Mar 10, 2025

Regarding throttling in v2, I added

    public static ushort MaximumIterationsPerSecond = 25;

In #3949

And set it to ushort max for benchmark.

But it is WIP, benchmark really needs better way of streaming the keys than TimedEvents

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants