在.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;
                }
            }
        }
    }
}

测试代码比较简单,就没有写注释.

在.Net 7下String的StartsWith和EndsWith性能测试

根据性能基准结果发现: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

源码是如何改进的

看一下StartsWith和EndsWith源码改进
看到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