2
0
Эх сурвалжийг харах

StripCRLF early exit when no newline to avoid StringBuilder allocation

Tonttu 4 сар өмнө
parent
commit
7d317ba550

+ 5 - 2
Benchmarks/Text/TextFormatter/StripCRLF.cs

@@ -4,6 +4,9 @@ using Tui = Terminal.Gui;
 
 namespace Terminal.Gui.Benchmarks.Text.TextFormatter;
 
+/// <summary>
+/// Benchmarks for <see cref="Tui.TextFormatter.StripCRLF"/> performance fine-tuning.
+/// </summary>
 [MemoryDiagnoser]
 public class StripCRLF
 {
@@ -31,7 +34,7 @@ public class StripCRLF
     }
 
     /// <summary>
-    /// Previous implementation with intermediate list allocation.
+    /// Previous implementation with intermediate rune list.
     /// </summary>
     private static string RuneListToString (string str, bool keepNewLine = false)
     {
@@ -98,7 +101,7 @@ public class StripCRLF
             "Nullam semper tempor mi, nec semper quam fringilla eu. Aenean sit amet pretium augue, in posuere ante. Aenean convallis porttitor purus, et posuere velit dictum eu."
         ];
 
-        bool[] newLinePermutations = { true, false };
+        bool[] newLinePermutations = [true, false];
 
         foreach (string text in textPermutations)
         {

+ 29 - 19
Terminal.Gui/Text/TextFormatter.cs

@@ -11,7 +11,7 @@ namespace Terminal.Gui;
 public class TextFormatter
 {
     // Utilized in CRLF related helper methods for faster newline char index search.
-    private static readonly SearchValues<char> NewLineSearchValues = SearchValues.Create(['\r', '\n']);
+    private static readonly SearchValues<char> NewlineSearchValues = SearchValues.Create(['\r', '\n']);
 
     private Key _hotKey = new ();
     private int _hotKeyPos = -1;
@@ -1191,31 +1191,39 @@ public class TextFormatter
     // TODO: Move to StringExtensions?
     internal static string StripCRLF (string str, bool keepNewLine = false)
     {
+        ReadOnlySpan<char> remaining = str.AsSpan ();
+        int firstNewlineCharIndex = remaining.IndexOfAny (NewlineSearchValues);
+        // Early exit to avoid StringBuilder allocation if there are no newline characters.
+        if (firstNewlineCharIndex < 0)
+        {
+            return str;
+        }
+
         StringBuilder stringBuilder = new();
+        ReadOnlySpan<char> firstSegment = remaining[..firstNewlineCharIndex];
+        stringBuilder.Append (firstSegment);
+
+        // The first newline is not yet skipped because the "keepNewLine" condition has not been evaluated.
+        // This means there will be 1 extra iteration because the same newline index is checked again in the loop.
+        remaining = remaining [firstNewlineCharIndex..];
 
-        ReadOnlySpan<char> remaining = str.AsSpan ();
         while (remaining.Length > 0)
         {
-            int nextLineBreakIndex = remaining.IndexOfAny (NewLineSearchValues);
-            if (nextLineBreakIndex == -1)
+            int newlineCharIndex = remaining.IndexOfAny (NewlineSearchValues);
+            if (newlineCharIndex == -1)
             {
-                if (str.Length == remaining.Length)
-                {
-                    return str;
-                }
-                stringBuilder.Append (remaining);
                 break;
             }
 
-            ReadOnlySpan<char> slice = remaining.Slice (0, nextLineBreakIndex);
-            stringBuilder.Append (slice);
+            ReadOnlySpan<char> segment = remaining[..newlineCharIndex];
+            stringBuilder.Append (segment);
 
+            int stride = segment.Length;
             // Evaluate how many line break characters to preserve.
-            int stride;
-            char lineBreakChar = remaining [nextLineBreakIndex];
-            if (lineBreakChar == '\n')
+            char newlineChar = remaining [newlineCharIndex];
+            if (newlineChar == '\n')
             {
-                stride = 1;
+                stride++;
                 if (keepNewLine)
                 {
                     stringBuilder.Append ('\n');
@@ -1223,10 +1231,11 @@ public class TextFormatter
             }
             else // '\r'
             {
-                bool crlf = (nextLineBreakIndex + 1) < remaining.Length && remaining [nextLineBreakIndex + 1] == '\n';
+                int nextCharIndex = newlineCharIndex + 1;
+                bool crlf = nextCharIndex < remaining.Length && remaining [nextCharIndex] == '\n';
                 if (crlf)
                 {
-                    stride = 2;
+                    stride += 2;
                     if (keepNewLine)
                     {
                         stringBuilder.Append ('\n');
@@ -1234,15 +1243,16 @@ public class TextFormatter
                 }
                 else
                 {
-                    stride = 1;
+                    stride++;
                     if (keepNewLine)
                     {
                         stringBuilder.Append ('\r');
                     }
                 }
             }
-            remaining = remaining.Slice (slice.Length + stride);
+            remaining = remaining [stride..];
         }
+        stringBuilder.Append (remaining);
         return stringBuilder.ToString ();
     }