首页 > 解决方案 > 不安全直接从数组创建字符串

问题描述

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

var array = str.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new string(array);

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

既然我知道array不会逃避这种方法,有没有办法使用不安全的代码来避免第二次分配?

标签: c#unsafe

解决方案


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

老套

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

跨度

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

不安全

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

仅不安全的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;
}

超级不安全

注意:这是为超级勇敢者准备的,其中字符串不倒转并且突变不是问题

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

基准

配置

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

结果

方法 ñ 意思是 错误 标准差 比率 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;
   }
}

推荐阅读