diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj
new file mode 100644
index 000000000..62f1be76c
--- /dev/null
+++ b/Benchmarks/Benchmarks.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net8.0
+ false
+ enable
+ enable
+ Terminal.Gui.$(MSBuildProjectName.Replace(" ", "_"))
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Benchmarks/Program.cs b/Benchmarks/Program.cs
new file mode 100644
index 000000000..3f017d29d
--- /dev/null
+++ b/Benchmarks/Program.cs
@@ -0,0 +1,20 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Running;
+
+namespace Terminal.Gui.Benchmarks;
+
+class Program
+{
+ static void Main (string [] args)
+ {
+ var config = DefaultConfig.Instance;
+
+ // Uncomment for faster but less accurate intermediate iteration.
+ // Final benchmarks should be run with at least the default run length.
+ //config = config.AddJob (BenchmarkDotNet.Jobs.Job.ShortRun);
+
+ BenchmarkSwitcher
+ .FromAssembly (typeof (Program).Assembly)
+ .Run(args, config);
+ }
+}
diff --git a/Benchmarks/Text/RuneExtensions/DecodeSurrogatePair.cs b/Benchmarks/Text/RuneExtensions/DecodeSurrogatePair.cs
new file mode 100644
index 000000000..5cce34424
--- /dev/null
+++ b/Benchmarks/Text/RuneExtensions/DecodeSurrogatePair.cs
@@ -0,0 +1,66 @@
+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