Skip to main content
AI Assist is now on Stack Overflow. Start a chat to get instant answers from across the network. Sign up to save and share your chats.
Used more standard formatting, etc.
Source Link
Peter Mortensen
  • 31.4k
  • 22
  • 110
  • 134

Using string.Create() and avoiding the throw keyword in our method (yes, you read it right), we can take Marcell's answer one step further. Also, my method handles strings of arbitrary length (e.g., several megabytes of text).

Here are the numbers for benchmarks run on .NET Core 3.1.7, x64.NET Core 3.1.7, 64 bit. I added a longer string to pinpoint the cost of extra copies.

Using string.Create() and avoiding the throw keyword in our method (yes, you read it right), we can take Marcell's answer one step further. Also, my method handles strings of arbitrary length (e.g. several megabytes of text).

Here are the numbers for benchmarks run on .NET Core 3.1.7, x64. I added a longer string to pinpoint the cost of extra copies.

Using string.Create() and avoiding the throw keyword in our method (yes, you read it right), we can take Marcell's answer one step further. Also, my method handles strings of arbitrary length (e.g., several megabytes of text).

Here are the numbers for benchmarks run on .NET Core 3.1.7, 64 bit. I added a longer string to pinpoint the cost of extra copies.

We have real tables now! - as a result, the diff looks more extensive than it really is - use view "Side-by-side markdown" to compare.
Source Link
Peter Mortensen
  • 31.4k
  • 22
  • 110
  • 134
| Method | Data | Mean | Error | StdDev | Median | |-------- |--------------------- |----------:|----------:|----------:|----------:| | L33t | red | 8.545 ns | 0.4612 ns | 1.3308 ns | 8.075 ns | | Marcell | red | 9.153 ns | 0.3377 ns | 0.9471 ns | 8.946 ns | | L33t | red house | 7.715 ns | 0.1741 ns | 0.4618 ns | 7.793 ns | | Marcell | red house | 10.537 ns | 0.5002 ns | 1.4351 ns | 10.377 ns | | L33t | red r(...)house [89] | 11.121 ns | 0.6774 ns | 1.9106 ns | 10.612 ns | | Marcell | red r(...)house [89] | 16.739 ns | 0.4468 ns | 1.3033 ns | 16.853 ns | 
MethodDataMeanErrorStdDevMedian
L33tred8.545 ns0.4612 ns1.3308 ns8.075 ns
Marcellred9.153 ns0.3377 ns0.9471 ns8.946 ns
L33tred house7.715 ns0.1741 ns0.4618 ns7.793 ns
Marcellred house10.537 ns0.5002 ns1.4351 ns10.377 ns
L33tred r(...)house [89]11.121 ns0.6774 ns1.9106 ns10.612 ns
Marcellred r(...)house [89]16.739 ns0.4468 ns1.3033 ns16.853 ns
using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; namespace CorePerformanceTest { class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<StringUpperTest>(); } } public class StringUpperTest { [Params("red", "red house", "red red red red red red red red red red red red red red red red red red red red red house")] public string Data;   [Benchmark] public string Marcell() => Data.Marcell(); [Benchmark] public string L33t() => Data.L33t(); } internal static class StringExtensions { public static string Marcell(this string s) { if (string.IsNullOrEmpty(s)) throw new ArgumentException("There is no first letter"); Span<char> a = stackalloc char[s.Length]; s.AsSpan(1).CopyTo(a.Slice(1)); a[0] = char.ToUpper(s[0]); return new string(a); }   public static string L33t(this string s) { static void ThrowError() => throw new ArgumentException("There is no first letter"); if (string.IsNullOrEmpty(s)) ThrowError(); // IMPORTANT: Do not "throw" here! return string.Create(s.Length, s, (chars, state) => { state.AsSpan().CopyTo(chars); chars[0] = char.ToUpper(chars[0]); }); } } } 
| Method | Data | Mean | Error | StdDev | Median | |-------- |--------------------- |----------:|----------:|----------:|----------:| | L33t | red | 8.545 ns | 0.4612 ns | 1.3308 ns | 8.075 ns | | Marcell | red | 9.153 ns | 0.3377 ns | 0.9471 ns | 8.946 ns | | L33t | red house | 7.715 ns | 0.1741 ns | 0.4618 ns | 7.793 ns | | Marcell | red house | 10.537 ns | 0.5002 ns | 1.4351 ns | 10.377 ns | | L33t | red r(...)house [89] | 11.121 ns | 0.6774 ns | 1.9106 ns | 10.612 ns | | Marcell | red r(...)house [89] | 16.739 ns | 0.4468 ns | 1.3033 ns | 16.853 ns | 
using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; namespace CorePerformanceTest { class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<StringUpperTest>(); } } public class StringUpperTest { [Params("red", "red house", "red red red red red red red red red red red red red red red red red red red red red house")] public string Data;   [Benchmark] public string Marcell() => Data.Marcell(); [Benchmark] public string L33t() => Data.L33t(); } internal static class StringExtensions { public static string Marcell(this string s) { if (string.IsNullOrEmpty(s)) throw new ArgumentException("There is no first letter"); Span<char> a = stackalloc char[s.Length]; s.AsSpan(1).CopyTo(a.Slice(1)); a[0] = char.ToUpper(s[0]); return new string(a); }   public static string L33t(this string s) { static void ThrowError() => throw new ArgumentException("There is no first letter"); if (string.IsNullOrEmpty(s)) ThrowError(); // IMPORTANT: Do not "throw" here! return string.Create(s.Length, s, (chars, state) => { state.AsSpan().CopyTo(chars); chars[0] = char.ToUpper(chars[0]); }); } } } 
MethodDataMeanErrorStdDevMedian
L33tred8.545 ns0.4612 ns1.3308 ns8.075 ns
Marcellred9.153 ns0.3377 ns0.9471 ns8.946 ns
L33tred house7.715 ns0.1741 ns0.4618 ns7.793 ns
Marcellred house10.537 ns0.5002 ns1.4351 ns10.377 ns
L33tred r(...)house [89]11.121 ns0.6774 ns1.9106 ns10.612 ns
Marcellred r(...)house [89]16.739 ns0.4468 ns1.3033 ns16.853 ns
using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; namespace CorePerformanceTest { class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<StringUpperTest>(); } } public class StringUpperTest { [Params("red", "red house", "red red red red red red red red red red red red red red red red red red red red red house")] public string Data; [Benchmark] public string Marcell() => Data.Marcell(); [Benchmark] public string L33t() => Data.L33t(); } internal static class StringExtensions { public static string Marcell(this string s) { if (string.IsNullOrEmpty(s)) throw new ArgumentException("There is no first letter"); Span<char> a = stackalloc char[s.Length]; s.AsSpan(1).CopyTo(a.Slice(1)); a[0] = char.ToUpper(s[0]); return new string(a); } public static string L33t(this string s) { static void ThrowError() => throw new ArgumentException("There is no first letter"); if (string.IsNullOrEmpty(s)) ThrowError(); // IMPORTANT: Do not "throw" here! return string.Create(s.Length, s, (chars, state) => { state.AsSpan().CopyTo(chars); chars[0] = char.ToUpper(chars[0]); }); } } } 
Source Link
l33t
  • 20.3k
  • 20
  • 116
  • 192

Using string.Create() and avoiding the throw keyword in our method (yes, you read it right), we can take Marcell's answer one step further. Also, my method handles strings of arbitrary length (e.g. several megabytes of text).

public static string L33t(this string s) { static void ThrowError() => throw new ArgumentException("There is no first letter"); if (string.IsNullOrEmpty(s)) ThrowError(); // No "throw" keyword to avoid costly IL return string.Create(s.Length, s, (chars, state) => { state.AsSpan().CopyTo(chars); // No slicing to save some CPU cycles chars[0] = char.ToUpper(chars[0]); }); } 

Performance

Here are the numbers for benchmarks run on .NET Core 3.1.7, x64. I added a longer string to pinpoint the cost of extra copies.

| Method | Data | Mean | Error | StdDev | Median | |-------- |--------------------- |----------:|----------:|----------:|----------:| | L33t | red | 8.545 ns | 0.4612 ns | 1.3308 ns | 8.075 ns | | Marcell | red | 9.153 ns | 0.3377 ns | 0.9471 ns | 8.946 ns | | L33t | red house | 7.715 ns | 0.1741 ns | 0.4618 ns | 7.793 ns | | Marcell | red house | 10.537 ns | 0.5002 ns | 1.4351 ns | 10.377 ns | | L33t | red r(...)house [89] | 11.121 ns | 0.6774 ns | 1.9106 ns | 10.612 ns | | Marcell | red r(...)house [89] | 16.739 ns | 0.4468 ns | 1.3033 ns | 16.853 ns | 

Full test code

using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; namespace CorePerformanceTest { class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<StringUpperTest>(); } } public class StringUpperTest { [Params("red", "red house", "red red red red red red red red red red red red red red red red red red red red red house")] public string Data; [Benchmark] public string Marcell() => Data.Marcell(); [Benchmark] public string L33t() => Data.L33t(); } internal static class StringExtensions { public static string Marcell(this string s) { if (string.IsNullOrEmpty(s)) throw new ArgumentException("There is no first letter"); Span<char> a = stackalloc char[s.Length]; s.AsSpan(1).CopyTo(a.Slice(1)); a[0] = char.ToUpper(s[0]); return new string(a); } public static string L33t(this string s) { static void ThrowError() => throw new ArgumentException("There is no first letter"); if (string.IsNullOrEmpty(s)) ThrowError(); // IMPORTANT: Do not "throw" here! return string.Create(s.Length, s, (chars, state) => { state.AsSpan().CopyTo(chars); chars[0] = char.ToUpper(chars[0]); }); } } } 

Please let me know if you can make it any faster!