diff --git a/Benchmarks/Text/StringExtensions/ToStringEnumerable.cs b/Benchmarks/Text/StringExtensions/ToStringEnumerable.cs new file mode 100644 index 000000000..43eef23ce --- /dev/null +++ b/Benchmarks/Text/StringExtensions/ToStringEnumerable.cs @@ -0,0 +1,70 @@ +using System.Text; +using BenchmarkDotNet.Attributes; +using Tui = Terminal.Gui; + +namespace Terminal.Gui.Benchmarks.Text.StringExtensions; + +/// +/// Benchmarks for performance fine-tuning. +/// +[MemoryDiagnoser] +public class ToStringEnumerable +{ + + /// + /// Benchmark for previous implementation. + /// + [Benchmark] + [ArgumentsSource (nameof (DataSource))] + public string Previous (IEnumerable runes, int size) + { + return StringAppendInLoop (runes); + } + + /// + /// Benchmark for current implementation with rune chars appending to StringBuilder. + /// + /// + /// + [Benchmark (Baseline = true)] + [ArgumentsSource (nameof (DataSource))] + public string Current (IEnumerable runes, int size) + { + return Tui.StringExtensions.ToString (runes); + } + + /// + /// Previous implementation with string append in a loop. + /// + private static string StringAppendInLoop (IEnumerable runes) + { + var str = string.Empty; + + foreach (Rune rune in runes) + { + str += rune.ToString (); + } + + return str; + } + + public IEnumerable DataSource () + { + string textSource = + """ + Ĺόŕéḿ íṕśúḿ d́όĺόŕ śít́ áḿét́, ćόńśéćt́ét́úŕ ád́íṕíśćíńǵ éĺít́. Ṕŕáéśéńt́ q́úíś ĺúćt́úś éĺít́. Íńt́éǵéŕ út́ áŕćú éǵét́ d́όĺόŕ śćéĺéŕíśq́úé ḿát́t́íś áć ét́ d́íáḿ. + Ṕéĺĺéńt́éśq́úé śéd́ d́áṕíb́úś ḿáśśá, v́éĺ t́ŕíśt́íq́úé d́úí. Śéd́ v́ít́áé ńéq́úé éú v́éĺít́ όŕńáŕé áĺíq́úét́. Út́ q́úíś όŕćí t́éḿṕόŕ, t́éḿṕόŕ t́úŕṕíś íd́, t́éḿṕúś ńéq́úé. + Ṕŕáéśéńt́ śáṕíéń t́úŕṕíś, όŕńáŕé v́éĺ ḿáúŕíś át́, v́áŕíúś śúśćíṕít́ áńt́é. Út́ ṕúĺv́íńáŕ t́úŕṕíś ḿáśśá, q́úíś ćúŕśúś áŕćú f́áúćíb́úś íń. + Óŕćí v́áŕíúś ńát́όq́úé ṕéńát́íb́úś ét́ ḿáǵńíś d́íś ṕáŕt́úŕíéńt́ ḿόńt́éś, ńáśćét́úŕ ŕíd́íćúĺúś ḿúś. F́úśćé át́ éx́ b́ĺáńd́ít́, ćόńv́áĺĺíś q́úáḿ ét́, v́úĺṕút́át́é ĺáćúś. + Śúśṕéńd́íśśé śít́ áḿét́ áŕćú út́ áŕćú f́áúćíb́úś v́áŕíúś. V́ív́áḿúś śít́ áḿét́ ḿáx́íḿúś d́íáḿ. Ńáḿ éx́ ĺéό, ṕh́áŕét́ŕá éú ĺόb́όŕt́íś át́, t́ŕíśt́íq́úé út́ f́éĺíś. + """; + + // Extra argument as workaround for the summary grouping different length collections to same baseline making comparison difficult. + int[] sizes = [1, 10, 100, textSource.Length / 2, textSource.Length]; + + foreach (int size in sizes) + { + yield return [textSource.EnumerateRunes ().Take (size).ToArray (), size]; + } + } +} diff --git a/Benchmarks/Text/TextFormatter/StripCRLF.cs b/Benchmarks/Text/TextFormatter/StripCRLF.cs index 055348e57..f12dd2831 100644 --- a/Benchmarks/Text/TextFormatter/StripCRLF.cs +++ b/Benchmarks/Text/TextFormatter/StripCRLF.cs @@ -77,7 +77,7 @@ public class StripCRLF } } - return StringExtensions.ToString (runes); + return Tui.StringExtensions.ToString (runes); } public IEnumerable DataSource () diff --git a/Terminal.Gui/Text/StringExtensions.cs b/Terminal.Gui/Text/StringExtensions.cs index 538b63975..4ad58912b 100644 --- a/Terminal.Gui/Text/StringExtensions.cs +++ b/Terminal.Gui/Text/StringExtensions.cs @@ -124,14 +124,16 @@ public static class StringExtensions /// public static string ToString (IEnumerable runes) { - var str = string.Empty; - + StringBuilder stringBuilder = new(); + const int maxCharsPerRune = 2; + Span charBuffer = stackalloc char[maxCharsPerRune]; foreach (Rune rune in runes) { - str += rune.ToString (); + int charsWritten = rune.EncodeToUtf16 (charBuffer); + ReadOnlySpan runeChars = charBuffer [..charsWritten]; + stringBuilder.Append (runeChars); } - - return str; + return stringBuilder.ToString (); } /// Converts a byte generic collection into a string in the provided encoding (default is UTF8)