在开发过程中,我们经常会遇到短字符串拼接的情况,例如需要打印状态信息,进度信息(“20/100”)等,那么多的拼接方法,哪个又好又快呢。今天写了一个benchmark看一下。
.Net测试代码
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using LinkDotNet.StringBuilder;
public enum TestState
{
Idle,
Running,
Jumping
}
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class IntToStringBenchmark
{
private readonly TestState _value = TestState.Idle;
private readonly int _maxValue = 100;
[Benchmark(Baseline = true)]
public string IntToString()
{
var val = $"{_value.ToString()}/{_maxValue.ToString()}";
return val;
}
[Benchmark]
public string IntInterpolation()
{
var val = $"{_value}/{_maxValue}";
return val;
}
[Benchmark]
public string IntStringBuilder()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(_value);
stringBuilder.Append("/");
stringBuilder.Append(_maxValue);
return stringBuilder.ToString();
}
[Benchmark]
public string IntValueStringBuilder()
{
using ValueStringBuilder stringBuilder = new();
stringBuilder.Append(_value);
stringBuilder.Append("/");
stringBuilder.Append(_maxValue);
return stringBuilder.ToString();
}
[Benchmark]
public string IntValueStringBuilderstackalloc()
{
using ValueStringBuilder stringBuilder = new(stackalloc char[8]);
stringBuilder.Append(_value.ToString());
stringBuilder.Append("/");
stringBuilder.Append(_maxValue);
return stringBuilder.ToString();
}
}
测试结果
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|
| IntToString | 23.84 ns | 0.391 ns | 0.365 ns | 1.00 | 0.00 | 0.0102 | 64 B | 1.00 |
| IntInterpolation | 42.59 ns | 0.485 ns | 0.453 ns | 1.79 | 0.03 | 0.0063 | 40 B | 0.62 |
| IntStringBuilder | 35.15 ns | 0.344 ns | 0.305 ns | 1.48 | 0.03 | 0.0268 | 168 B | 2.62 |
| IntValueStringBuilder | 70.04 ns | 1.132 ns | 1.059 ns | 2.94 | 0.06 | 0.0101 | 64 B | 1.00 |
| IntValueStringBuilderstackalloc | 69.39 ns | 0.459 ns | 0.430 ns | 2.91 | 0.04 | 0.0101 | 64 B | 1.00 |
结论
短字符串用StringBuilder有点多此一举了
64 B 基本上就是结果字符串的大小
测试例子中,直接使用内插字符串内存分配最少,只有40 B
所以一般情况下,直接用内插字符串最方便
看一下sharplab

可以看到,在高版本的.Net中,内插字符串会转化为DefaultInterpolatedStringHandler,这是一个结构体处理器。
而在内插字符串中执行ToString()会导致退化成string.Concat()。
Unity测试代码
using System.Text;
using Cysharp.Text;
using NUnit.Framework;
using Unity.PerformanceTesting;
public enum TestState
{
Idle,
Running,
Jumping
}
public class IntToStringBenchmark
{
private readonly TestState _value = TestState.Idle;
private readonly int _maxValue = 100;
[Test, Performance]
public void IntToString()
{
Measure.Method(() =>
{
var val = $"{_value.ToString()}/{_maxValue.ToString()}";
_ = val; // 防止编译器优化掉结果
})
.WarmupCount(10)
.MeasurementCount(100)
.GC()
.Run();
}
[Test, Performance]
public void IntInterpolation()
{
Measure.Method(() =>
{
var val = $"{_value}/{_maxValue}";
_ = val;
})
.WarmupCount(10)
.MeasurementCount(100)
.GC()
.Run();
}
[Test, Performance]
public void IntStringBuilder()
{
Measure.Method(() =>
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(_value.ToString());
stringBuilder.Append("/");
stringBuilder.Append(_maxValue);
var val = stringBuilder.ToString();
_ = val;
})
.WarmupCount(10)
.MeasurementCount(100)
.GC()
.Run();
}
[Test, Performance]
public void IntStringZString()
{
Measure.Method(() =>
{
using var stringBuilder = ZString.CreateStringBuilder(true);
stringBuilder.Append(_value.ToString());
stringBuilder.Append("/");
stringBuilder.Append(_maxValue);
var val = stringBuilder.ToString();
_ = val;
})
.WarmupCount(10)
.MeasurementCount(100)
.GC()
.Run();
}
}
测试结果
IntInterpolation
Time.GC() in Undefineds
Min: 4.00
Median: 4.00
Max: 11.00
Avg: 4.20
StdDev: 0.77
SampleCount: 110
Sum: 462.00
Time in Milliseconds
Min: 0.02 ms
Median: 0.02 ms
Max: 0.03 ms
Avg: 0.02 ms
StdDev: 0.00 ms
SampleCount: 100
Sum: 2.17 ms
IntStringBuilder
Time.GC() in Undefineds
Min: 6.00
Median: 6.00
Max: 9.00
Avg: 6.10
StdDev: 0.45
SampleCount: 110
Sum: 671.00
Time in Milliseconds
Min: 0.02 ms
Median: 0.02 ms
Max: 0.04 ms
Avg: 0.02 ms
StdDev: 0.00 ms
SampleCount: 100
Sum: 2.23 ms
IntStringZString
Time.GC() in Undefineds
Min: 3.00
Median: 3.00
Max: 14.00
Avg: 3.11
StdDev: 1.05
SampleCount: 110
Sum: 342.00
Time in Milliseconds
Min: 0.02 ms
Median: 0.02 ms
Max: 0.03 ms
Avg: 0.02 ms
StdDev: 0.00 ms
SampleCount: 100
Sum: 1.96 ms
IntToString
Time.GC() in Undefineds
Min: 4.00
Median: 4.00
Max: 7.00
Avg: 4.05
StdDev: 0.31
SampleCount: 110
Sum: 445.00
Time in Milliseconds
Min: 0.01 ms
Median: 0.02 ms
Max: 0.03 ms
Avg: 0.02 ms
StdDev: 0.00 ms
SampleCount: 100
Sum: 1.80 ms
结论
内插字符串在内存分配和速度上的表现依然很好。
ZString库虽然慢了一丢丢,但是内存分配少了很多。

4406

被折叠的 条评论
为什么被折叠?



