在.Net 7中使用Span下IndexOfAny和IndexOfAnyExcept

前言

这篇文章其实比 在.Net 7 String的StartsWtih和EndsWith性能改进 时间上还要早一些,只是一直处于没完成的状态.进入正题.主要是看到这个Issue:
  1. Add initial version of {Last}IndexOfAnyExcept (#67941)

在项目中经常使用IndexOf/Contains判断字符串是否包含某个字符/字符串,一直没有是用IndexOfAny(.Net Core 2.0新增).而IndexOfAnyExcep是在.Net 7中新增的,这两个API都是字符串开始查找的,与这两个API相反的,分别是LastIndexOfAny(.Net Core 2.1新增)和LastIndexOfAnyExcept(.Net 7新增),这两个都是从字符串的末尾开始查找的.

这里只是测试前两个IndexOfAny和IndexOfAnyExcept.

测试代码

using System;
using BenchmarkDotNet.Attributes;

namespace CSharpBenchmarks.SpanTest
{
    [MemoryDiagnoser]
    [DisassemblyDiagnoser(printSource: true)]
    public class IndexOfAnyExceptTest
    {
        [Params(1024, 2048)]
        public int Times { get; set; }


        [Params("hello world\t", "\thello csharp")]
        public string Chars { get; set; }

        [Benchmark]
        public int ForIndexOfTest()
        {
            int sum = 0;
            for (int i = 0; i < Times; i++)
            {
                for (int j = 0; j < Chars?.Length; j++)
                {
                    char c = Chars[j];
                    if (c == '\t' || c == '\n' || c == '\r')
                    {
                        sum += 1;   //这里只是不让JIT将代码优化掉
                        break;
                    }
                }
            }
            return sum;
        }

        [Benchmark]
        public int SpanIndexOfAnyTest()
        {
            int sum = 0;
            for (int i = 0; i < Times; i++)
            {
                if (Chars.AsSpan().IndexOfAny("\t\r\n") > -1)
                {
                    sum += 1;       //这里只是不让JIT将代码优化掉
                }
            }
            return sum;
        }

        [Benchmark]
        public int ForNoContainsTest()
        {
            int sum = 0;
            for (int i = 0; i < Times; i++)
            {
                for (int j = 0; j < Chars?.Length; j++)
                {
                    char c = Chars[j];
                    if (c != '\t' || c != '\n' || c != '\r')
                    {
                        sum += 1;    //这里只是不让JIT将代码优化掉
                        break;
                    }
                }
            }
            return sum;
        }

        [Benchmark]
        public int SpanIndexOfAnyExceptTest()
        {
            int sum = 0;
            for (int i = 0; i < Times; i++)
            {
                if (Chars.AsSpan().IndexOfAnyExcept("\t\r\n") > -1)
                {
                    sum += 1;       //这里只是不让JIT将代码优化掉
                }
            }
            return sum;
        }
    }
}

使用BenchmarkDotNet测试for循环和IndexOfAny性能差异
根据上图的测试结果: IndexOfAny和IndexOfAnyExcept在最好的情况下(\thello csharp),是比我们直接循环要慢上不少(真实情况下,会从末位开始遍历),但在最坏的情况(hello world\t),IndexOfAny竟然比我们直接循环快上不少.IndexOfAny和IndexOfAnyExcept在不需要性能的地方还是可以使用的.

秋风 2022-07-04