😽

.NET Core 2.1 で String.Split は早くなっているか?

2022/10/18に公開

Qiitaより転載
2018/4/26 初投稿


最近 .NET Core 2.1 preview が盛んらしいので実験。

そういえばどこかで .NET Core 2.1 では Span<T> が入るから、String.Split が早くなる、みたいなことを聞いた気がしたのでベンチマーク。

テストコード

/* using は省略 */

namespace csharp
{
    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<Test>(new BenchmarkConfig());
        }
    }

    public class BenchmarkConfig : ManualConfig
    {
        public BenchmarkConfig()
        {
            Add(SetRun(Job.Default.UnfreezeCopy()));

            Add(DefaultColumnProviders.Instance);
            Add(MarkdownExporter.GitHub);
            Add(new ConsoleLogger());
            Add(new HtmlExporter());
            Add(MemoryDiagnoser.Default);
        }

        private static Job SetRun(Job job)
        {
            job.Run.UnrollFactor = 5;
            job.Run.InvocationCount = 5;
            job.Run.WarmupCount = 1;
            job.Run.TargetCount = 1;
            job.Run.LaunchCount = 30;
            return job;
        }
    }

    public class Test
    {
        private readonly string Str = "hoge,fuga,moge";

        [Benchmark]
        public (string, string) UsingSplit()
        {
            var split = Str.Split(",");
            return (split[0], split[2]);
        }

        [Benchmark]
        public (string, string) UsingSpanSplit()
        {
            IEnumerable<Span> LineSplitter(string s, char separator)
            {
                int offset = 0;
                int length = 0;
                for (int i = 0; i < s.Length; i++)
                {
                    length++;
                    if (s[i] == separator)
                    {
                        yield return new Span { Offset = offset, Length = length - 1, Value = s };
                        offset = i + 1;
                        length = 0;
                    }
                }
            }

            using(var enumerator = LineSplitter(Str, ',').GetEnumerator())
            {
                enumerator.MoveNext();
                var a = enumerator.Current.Substring();
                enumerator.MoveNext();
                enumerator.MoveNext();
                var b = enumerator.Current.Substring();
                return (a, b);
            }
        }
    }

    public struct Span
    {
        public string Value { get; set; }
        public int Offset { get; set; }
        public int Length { get; set; }

        public string Substring() => Value.Substring(Offset, Length);
    }
}

"hoge,fuga,moge" を分割して、"hoge""moge" だけ持ってくる時間を計ってみました。

普通にテストするだけだと面白くないので、オレオレ Span を使った実装も含んでます。MoveNext があんま美しくないけど。

結果

.NET Core 2.0

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.16299.371 (1709/FallCreatorsUpdate/Redstone3)
Intel Core i5-5200U CPU 2.20GHz (Broadwell), 1 CPU, 4 logical and 2 physical cores
Frequency=2143478 Hz, Resolution=466.5315 ns, Timer=TSC
.NET Core SDK=2.1.300-preview2-008533
  [Host]     : .NET Core 2.0.6 (CoreCLR 4.6.26212.01, CoreFX 4.6.26212.01), 64bit RyuJIT
  Job-FMXLHV : .NET Core 2.0.6 (CoreCLR 4.6.26212.01, CoreFX 4.6.26212.01), 64bit RyuJIT

InvocationCount=5  LaunchCount=30  TargetCount=1
UnrollFactor=5  WarmupCount=1
     Method |     Mean |     Error |    StdDev | Allocated |

--------------- |---------:|----------:|----------:|----------:|
UsingSplit | 1.439 us | 0.6936 us | 1.0381 us | 0 B |
UsingSpanSplit | 1.046 us | 0.3128 us | 0.4681 us | 0 B |

.NET Core 2.1

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.16299.371 (1709/FallCreatorsUpdate/Redstone3)
Intel Core i5-5200U CPU 2.20GHz (Broadwell), 1 CPU, 4 logical and 2 physical cores
Frequency=2143478 Hz, Resolution=466.5315 ns, Timer=TSC
.NET Core SDK=2.1.300-preview2-008533
  [Host]     : .NET Core 2.1.0-preview2-26406-04 (CoreCLR 4.6.26406.07, CoreFX 4.6.26406.04), 64bit RyuJIT
  Job-VJIIGD : .NET Core 2.1.0-preview2-26406-04 (CoreCLR 4.6.26406.07, CoreFX 4.6.26406.04), 64bit RyuJIT

InvocationCount=5  LaunchCount=30  TargetCount=1
UnrollFactor=5  WarmupCount=1
     Method |     Mean |     Error |    StdDev | Allocated |

--------------- |---------:|----------:|----------:|----------:|
UsingSplit | 1.175 us | 0.3757 us | 0.5624 us | 0 B |
UsingSpanSplit | 1.005 us | 0.3680 us | 0.5508 us | 0 B |

うむ、20% 近く早くなってるね。でもこれは Split が早くなってるのかランタイムが早くなってるのかわからんね。
オレオレ SpanSplit は関係なく早かった。Substring 自体が遅いのかな、と想像。
Split の実装見たほうが早そう。

Discussion