using System.Text; using BenchmarkDotNet.Attributes; using Tui = Terminal.Gui; namespace Terminal.Gui.Benchmarks.Text.RuneExtensions; /// /// Benchmarks for performance fine-tuning. /// [MemoryDiagnoser] [BenchmarkCategory (nameof (Tui.RuneExtensions))] public class DecodeSurrogatePair { /// /// Benchmark for previous implementation. /// /// /// [Benchmark] [ArgumentsSource (nameof (DataSource))] public char []? Previous (Rune rune) { _ = RuneToStringToCharArray (rune, out char []? chars); return chars; } /// /// Benchmark for current implementation. /// /// Utilizes Rune methods that take Span argument avoiding intermediate heap array allocation when combined with stack allocated intermediate buffer. /// When rune is not surrogate pair there will be no heap allocation. /// /// Final surrogate pair array allocation cannot be avoided due to the current method signature design. /// Changing the method signature, or providing an alternative method, to take a destination Span would allow further optimizations by allowing caller to reuse buffer for consecutive calls. /// [Benchmark (Baseline = true)] [ArgumentsSource (nameof (DataSource))] public char []? Current (Rune rune) { _ = Tui.RuneExtensions.DecodeSurrogatePair (rune, out char []? chars); return chars; } /// /// Previous implementation with intermediate string allocation. /// /// The IsSurrogatePair implementation at the time had hidden extra string allocation so there were intermediate heap allocations even if rune is not surrogate pair. /// private static bool RuneToStringToCharArray (Rune rune, out char []? chars) { if (rune.IsSurrogatePair ()) { chars = rune.ToString ().ToCharArray (); return true; } chars = null; return false; } public static IEnumerable DataSource () { yield return new Rune ('a'); yield return "𝔹".EnumerateRunes ().Single (); } }