在.Net 7 String的StartsWtih和EndsWith性能改进
前言
在.Net 7 String下StartsWith和EndsWith两个方法是有性能优化的,具体可以看:惯例先进行性能测试
using BenchmarkDotNet.Attributes;
namespace CSharpBenchmarks.StringTest
{
[MemoryDiagnoser]
[DisassemblyDiagnoser(printSource: true)]
public class StringEndsWith
{
string value = "this, is, a, very long string, with some spaces, commas and more spaces";
[Params(1024, 2048)]
public int Times { get; set; }
[Params('t', 's')]
public char Chars { get; set; }
[Benchmark]
public void EndsWithTest()
{
int count = 0;
for (int i = 0; i < Times; i++)
{
if (value.EndsWith(Chars))
{
count += 1;
}
}
}
[Benchmark]
public void StartsWithTest()
{
int count = 0;
for (int i = 0; i < Times; i++)
{
if (value.StartsWith(Chars))
{
count += 1;
}
}
}
}
}
测试代码比较简单,就没有写注释.
根据性能基准结果发现:EndsWith性能提升的高一些,在.Net7中只是.Net 6的三分之一左右.StartsWith只有匹配为真时,提升40%多,匹配为假时,性能提升只有13%左右.生成代码大小在.Net 7中都有不同的减少.接下来我们看看EndsWith生成的汇编代码:
; 在.Net 6 EndsWithTest生成的汇编代码
; CSharpBenchmarks.StringTest.StringEndsWith.EndsWithTest()
push rdi
push rsi
push rbx
sub rsp,20
mov rsi,rcx
xor edi,edi
xor ebx,ebx
cmp dword ptr [rsi+10],0
jle short M00_L02
M00_L00:
mov rcx,[rsi+8]
movzx edx,word ptr [rsi+14]
cmp [rcx],ecx
call System.String.EndsWith(Char) ;在调用String.EndsWith代码没有进行内联
test eax,eax
je short M00_L01
inc edi
M00_L01:
inc ebx
cmp ebx,[rsi+10]
jl short M00_L00
M00_L02:
add rsp,20
pop rbx
pop rsi
pop rdi
ret
; Total bytes of code 56
; System.String.EndsWith(Char)
mov eax,[rcx+8]
lea r8d,[rax+0FFFF]
cmp eax,r8d
jbe short M01_L00
movsxd rax,r8d
movzx eax,word ptr [rcx+rax*2+0C]
movzx edx,dx
cmp eax,edx
sete al
movzx eax,al
ret
M01_L00:
xor eax,eax
ret
; Total bytes of code 35
; 在.Net 7 EndsWithTest生成汇编代码
; 在汇编代码中,没有调用EndsWith是JIT将EndsWith进行内联
; CSharpBenchmarks.StringTest.StringEndsWith.EndsWithTest()
push rdi
push rsi
xor eax,eax
xor edx,edx
mov r8d,[rcx+10]
test r8d,r8d
jle short M00_L02
mov r9,[rcx+8]
movzx ecx,word ptr [rcx+14]
M00_L00:
mov r10,r9
mov r11d,ecx
mov esi,[r10+8]
lea edi,[rsi+0FFFF]
cmp esi,edi
jbe short M00_L01
mov esi,edi
movzx r10d,word ptr [r10+rsi*2+0C] ;这行是不是在上方EndsWith汇编代码看到过
cmp r10d,r11d
jne short M00_L01
inc eax
M00_L01:
inc edx
cmp edx,r8d
jl short M00_L00
M00_L02:
pop rsi
pop rdi
ret
; Total bytes of code 65
源码是如何改进的

看到RuntimeHelpers.IsKnownConstant是不是感觉有点熟悉.这个配方其实原先见到过:在.Net 7中Math.Round性能优化
看一下RuntimeHelpers.IsKnownConstant代码:
#pragma warning disable IDE0060
[Intrinsic]
internal static bool IsKnownConstant(string? t) => false;
[Intrinsic]
internal static bool IsKnownConstant(char t) => false;
[Intrinsic]
internal static bool IsKnownConstant(int t) => false;
#pragma warning restore IDE0060
RuntimeHelpers.IsKnownConstant源码其实很简单,主要是Intrinsic特性(或者叫注解),由JIT在编译时确定是否为常量.
带来的好处
public void OkTest()
{
//以往这样写的代码
if (value[0] == Chars)
{
}
if (value[value.Length - 1] == Chars)
{
}
//⬇⬇都可以分别有StartsWith和EndsWith
if (value.StartsWith(Chars))
{
}
if (value.EndsWith(Chars))
{
}
}
在.Net 7 String下StartsWith和EndsWith性能得到了提升,可以替代以往使用下标的方式,还减少了方法调用(JIT将方法内联了).
秋风
2022-06-19