C# のローカル関数は果たして早いのか
Qiitaより転載
2018/4/27 初投稿
C# 7 になってからローカル関数が導入されて、場合によってはラムダ式より早いらしいという話を聞きました。
最近ベンチマークおじさんをやっているので、早速ベンチマークしてみたいと思います。
.NET Core 2.1 で String.Split は早くなっているか?
これ、Span<T>
がそもそも実装されてるんだからそれ使えばいいじゃん、と思ったので、それを使ってベンチマークします。
テスト内容
前回と同じく "hoge,fuga,moge"
という文字列から "hoge"
と "moge"
を取得します。
テストコード
/* using 省略*/
namespace SpanTest
{
class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<Test>(new BenchmarkConfig());
}
}
public class Test
{
private readonly string Str = "hoge,fuga,moge";
[Benchmark]
public (string, string) UsingSplit()
{
/* 前回と同じなので省略 */
}
[Benchmark]
public (string, string) UsingSpanSplit()
{
/* 前回と同じなので省略 */
}
[Benchmark]
public (string, string) UseFastSpanWithLocalMethod()
{
ReadOnlySpan<char> span = Str.AsSpan();
char separator = ',';
int offset = 0;
ReadOnlySpan<char> GetNextValue(ref ReadOnlySpan<char> strSpan)
{
int length = 0;
int startPos = offset;
for (int i = offset; i < strSpan.Length; i++)
{
length++;
if (strSpan[i] == separator)
{
offset = i + 1;
break;
}
}
return strSpan.Slice(startPos, length);
}
var a = new string(GetNextValue(ref span));
GetNextValue(ref span);
var b = new string(GetNextValue(ref span));
return (a, b);
}
[Benchmark]
public (string, string) UseFastSpan()
{
ReadOnlySpan<char> span = Str.AsSpan();
char separator = ',';
int offset = 0;
var a = new string(GetNextValue(ref span, separator, ref offset));
GetNextValue(ref span, separator, ref offset);
var b = new string(GetNextValue(ref span, separator, ref offset));
return (a, b);
}
private ReadOnlySpan<char> GetNextValue(ref ReadOnlySpan<char> strSpan, char separator, ref int offset)
{
int length = 0;
int startPos = offset;
for (int i = offset; i < strSpan.Length; i++)
{
length++;
if (strSpan[i] == separator)
{
offset = i + 1;
break;
}
}
return strSpan.Slice(startPos, length);
}
}
public readonly struct MySpan
{
/* 前回と同じなので省略 */
}
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;
}
}
}
Span<T>
を使う際、前回のオレオレSpan と同じ方法でローカル関数を使った方法と、ref でプライベート関数に必要な値を渡す方法とで、比較してみました。
結果
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-SFFTDC : .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,063.6 ns | 374.8 ns | 561.0 ns | 0 B |
UsingSpanSplit | 765.6 ns | 135.8 ns | 203.2 ns | 0 B |
UseFastSpanWithLocalMethod | 761.2 ns | 268.8 ns | 402.3 ns | 0 B |
UseFastSpan | 688.5 ns | 136.4 ns | 204.1 ns | 0 B |
誤差範囲内ですかね。一応ローカル関数を使っても処理が遅くなることはない、ということが分かった気がします。
Split
おせぇ。。
おまけ
自分で Split
作ったほうが早くね?
ということで作ってみた
public static class StringExtensions
{
public static string[] FastSplit(this string s, char separator)
{
int sectionCount = 1;
for(int i = 0; i < s.Length; i++)
{
if(s[i] == separator)
{
sectionCount++;
}
}
var sSpan = s.AsSpan();
var result = new string[sectionCount];
int index = 0;
int offset = 0;
int length = 0;
for(int i = 0; i < s.Length; i++)
{
length++;
if(s[i] == separator)
{
result[index++] = new string(sSpan.Slice(offset, length - 1));
length = 0;
offset = i + 1;
}
}
result[index] = new string(sSpan.Slice(offset, length));
return result;
}
}
Method | Mean | Error | StdDev | Allocated |
--------------- |-----------:|---------:|---------:|----------:|
UsingSplit | 1,057.0 ns | 168.6 ns | 252.4 ns | 0 B |
UsingFastSplit | 872.9 ns | 151.5 ns | 226.8 ns | 0 B |
早くなりましたとさ。おわり。
Discussion