直接从数组创建字符串不安全

Yai*_*adt 1 c# unsafe

目前我知道的将字符串的第一个字母大写的最快方法如下:

var array = str.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new string(array);
Run Code Online (Sandbox Code Playgroud)

这涉及 2 个数组分配:char.ToUpper(array[0])以及复制到new string(array).

因为我知道array不能逃避这个方法,有没有办法使用不安全的代码来避免第二次分配?

AAA*_*ddd 5

既然您要求“最快”的方式,那么让我们对一些事情进行基准测试。毕竟,我们作为程序员喜欢使用经验证据*令人信服地点头*

老套

public string OldSchool(string value) 
   => char.ToUpper(value[0]) + value[1..];
Run Code Online (Sandbox Code Playgroud)

跨度

public string TestSpan(string value)
   => string.Create(value.Length, value, (span, str) =>
   {
      str.AsSpan().CopyTo(span);
      span[0] = char.ToUpper(span[0]);
   });
Run Code Online (Sandbox Code Playgroud)

不安全

public unsafe string TestUnsafe(string value)
{
   var result = new string(value);
   fixed (char* p = result) p[0] = char.ToUpper(p[0]);
   return result;
}
Run Code Online (Sandbox Code Playgroud)

仅限不安全的 Ascii

public unsafe string TestUnsafeAscii(string value)
{
   var result = new string(value);
   fixed (char* p = result) 
      if(p[0] >= 'a' && p[0] <= 'z') *p += (char)32;
   return result;
}
Run Code Online (Sandbox Code Playgroud)

超级不安全

注意:这是针对超级勇敢者的,其中弦不内转并且突变不是问题

public unsafe string SuperUnsafe(string value)
{
   fixed (char* p = value) p[0] = char.ToUpper(p[0]);
   return value;
}
Run Code Online (Sandbox Code Playgroud)

基准测试

配置

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.804 (2004/?/20H1)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET Core SDK=5.0.201
  [Host]        : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
  .NET Core 5.0 : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT

Job=.NET Core 5.0  Runtime=.NET Core 5.0
Run Code Online (Sandbox Code Playgroud)

结果

方法 意思是 错误 标准差 比率 第0代 第一代 第2代 已分配
老套 5 44.89纳秒 0.389纳秒 0.364纳秒 1.00 0.0105 - - 88乙
跨度 5 26.37纳秒 0.170纳秒 0.159纳秒 1.00 0.0038 - - 32乙
不安全 5 25.15纳秒 0.128纳秒 0.119纳秒 1.00 0.0038 - - 32乙
不安全的Ascii 5 11.92纳秒 0.093纳秒 0.073纳秒 1.00 0.0038 - - 32乙
超级不安全 5 10.22纳秒 0.051纳秒 0.045纳秒 1.00 - - - -
方法 意思是 错误 标准差 比率 第0代 第一代 第2代 已分配
老套 10 49.31纳秒 0.595纳秒 0.527纳秒 1.00 0.0134 - - 112乙
跨度 10 27.71纳秒 0.548纳秒 0.512纳秒 1.00 0.0057 - - 48乙
不安全 10 26.76纳秒 0.142纳秒 0.126纳秒 1.00 0.0057 - - 48乙
不安全的Ascii 10 13.40纳秒 0.103纳秒 0.096纳秒 1.00 0.0057 - - 48乙
超级不安全 10 10.28纳秒 0.106纳秒 0.094纳秒 1.00 - - - -
方法 意思是 错误 标准差 比率 第0代 第一代 第2代 已分配
老套 100 83.52纳秒 0.966纳秒 0.903纳秒 1.00 0.0564 - - 472 乙
跨度 100 45.77纳秒 0.441纳秒 0.412纳秒 1.00 0.0268 - - 224乙
不安全 100 44.07纳秒 0.511纳秒 0.453纳秒 1.00 0.0268 - - 224乙
不安全的Ascii 100 31.45纳秒 0.382纳秒 0.357纳秒 1.00 0.0268 - - 224乙
超级不安全 100 10.26纳秒 0.078纳秒 0.073纳秒 1.00 - - - -
方法 意思是 错误 标准差 比率 第0代 第一代 第2代 已分配
老套 1000 512.05纳秒 2.909纳秒 2.578纳秒 1.00 0.4864 0.0052 - 4072乙
跨度 1000 260.35纳秒 2.593纳秒 2.425纳秒 1.00 0.2418 0.0017 - 2024乙
不安全 1000 255.06纳秒 1.587纳秒 1.407纳秒 1.00 0.2418 0.0017 - 2024乙
不安全的Ascii 1000 247.60纳秒 2.500纳秒 2.338纳秒 1.00 0.2418 0.0017 - 2024乙
超级不安全 1000 10.21纳秒 0.060纳秒 0.056纳秒 1.00 - - - -

完整测试代码

public class Test
{

   private string _data;

   private static readonly Random random = new Random(42);

   public static string RandomString(int length)
   {
      const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      return new string(Enumerable.Repeat(chars, length)
         .Select(s => s[random.Next(s.Length)]).ToArray());
   }

   [Params(5, 10, 100, 1000)] public int N;

   [GlobalSetup]
   public void Setup()
   {
      _data = RandomString(N);
   }

   [Benchmark]
   public string OldSchool() => OldSchool(_data);

   public string OldSchool(string value)
      => char.ToUpper(value[0]) + value[1..];

   [Benchmark]
   public string Span() => TestSpan(_data);

   public string TestSpan(string value)
      => string.Create(value.Length, value, (span, str) =>
      {
         str.AsSpan().CopyTo(span);
         span[0] = char.ToUpper(span[0]);
      });

   [Benchmark]
   public string Unsafe() => TestUnsafe(_data);

   public unsafe string TestUnsafe(string value)
   {
      var result = new string(value);
      fixed (char* p = result) p[0] = char.ToUpper(p[0]);
      return result;
   }

   [Benchmark]
   public unsafe string SuperUnsafe() => SuperUnsafe(_data);

   public unsafe string SuperUnsafe(string value)
   {
      fixed (char* p = value) p[0] = char.ToUpper(p[0]);
      return value;
   }

   [Benchmark]
   public string UnsafeAscii() => TestUnsafeAscii(_data);

   public unsafe string TestUnsafeAscii(string value)
   {
      var result = new string(value);
      fixed (char* p = result) 
         if(p[0] >= 'a' && p[0] <= 'z') *p += (char)32;
      return result;
   }
}
Run Code Online (Sandbox Code Playgroud)