c# - 不安全直接从数组创建字符串
问题描述
我知道将字符串的第一个字母大写的当前最快方法如下:
var array = str.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new string(array);
这涉及 2 个数组分配:char.ToUpper(array[0])
以及复制到new string(array)
.
既然我知道array
不会逃避这种方法,有没有办法使用不安全的代码来避免第二次分配?
解决方案
既然您要求“最快”的方式,让我们对几件事进行基准测试。毕竟,作为程序员,我们喜欢使用经验证据 *令人信服地点头*
老套
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;
}
}
推荐阅读
- javascript - 如何通过按一次按钮开始然后停止 CSS 动画,然后重做?
- r - 使用 apply 在函数中添加条件
- html - 使用 flexbox 可以实现这种响应式布局吗?
- c - 将堆栈上的函数返回地址更改为另一个函数,然后返回主函数
- linux - linux find 命令:在多个目录中多次搜索
- javascript - 如何防范 Angular 9 AOT
- python - 您如何搜索列表,如果找到该值,则遵循 if 语句一次,而不是针对找到的每个值
- elasticsearch - Elasticsearch 显示不应该显示的结果
- scala - java.util.UUID.randomUUID 是可复制的吗?
- javascript - 如何访问 JSON 有效负载数据的内部?