Skip to content

Instantly share code, notes, and snippets.

@ufcpp
Created November 26, 2017 06:41
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ufcpp/b2e64d8e0165746effbd98b8aa955f7e to your computer and use it in GitHub Desktop.
Save ufcpp/b2e64d8e0165746effbd98b8aa955f7e to your computer and use it in GitHub Desktop.
静的メソッドをデリゲート化した場合の呼び出し負荷、思ったよりもでかかった
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
static class Lambda
{
// Roslyn 実装では、ラムダ式に対して匿名クラスが作られて、インスタンス メソッドが作られる。
// インスタンスを作る分重たそうに見えるけど、実はこの方が速い。というか静的メソッドが遅い。
public static Action Nop { get; } = () => { };
}
static class Static
{
// Action デリゲートに静的メソッドを渡す。
// 静的メソッドなデリゲート、かなり遅い。
public static Action Nop { get; } = nop;
private static void nop() { }
}
static class Curried
{
// ダミー引数を作って、拡張メソッドな書き方でデリゲートを作る。
// こうすると、Delegate の中で、静的メソッドだけどインスタンス メソッドと同じ呼び方されるようになる。
// 実はこの書き方が最速。
// (Delegate の中で起こってる処理は Lambda と同じになる。一方で無駄な匿名クラスのインスタンス生成もされなくなる。)
public static Action Nop { get; } = default(object).nop;
private static void nop(this object dummy) { }
}
/// <summary>
/// 実行例。
/// Method | Mean | Error | StdDev |
/// ----------- |----------:|----------:|----------:|
/// LambdaNop | 2.987 us | 0.0088 us | 0.0082 us |
/// StaticNop | 18.460 us | 0.0868 us | 0.0811 us |
/// CurriedNop | 2.985 us | 0.0053 us | 0.0045 us |
///
/// static ほんとに遅い…
/// Delegate の中身の問題。
/// https://source.dot.net/#System.Private.CoreLib/src/System/Delegate.cs,19
///
/// Delegate の中身、インスタンス メソッドの方を基準に最適化されてて、
/// 静的メソッドの場合には1段階ラッパーメソッドが挟まっちゃうらしい。
/// (メソッドにジャンプする際に引数をどうやって並べるかとかの規約が違うんで、引数の並べ替えコードが必要。)
///
/// なので、C# 6.0 以降、何もキャプチャしてないラムダ式でも、インスタンス メソッドが作られるようになった。
/// (今回の計測だと6倍差がついてるわけで、相当な性能差がある。匿名クラスのインスタンスができちゃう負担の方がはるかに小さい。)
///
/// 当初案では、<see cref="Curried"/>みたいな curried delegate を使う実装も考えられたらしいけども。
/// <see cref="Delegate.Method"/>に謎のダミー引数が増えちゃうんで、リフレクション使いまくりな Moq みたいなライブラリで問題が起きたらしい。
/// https://roslyn.codeplex.com/workitem/246
/// </summary>
public class NopBenchmark
{
[Benchmark]
public void LambdaNop() => Run(Lambda.Nop);
[Benchmark]
public void StaticNop() => Run(Static.Nop);
[Benchmark]
public void CurriedNop() => Run(Curried.Nop);
private void Run(Action action)
{
for (int i = 0; i < 1000; i++)
{
action();
}
}
}
class Program
{
static void Main()
{
BenchmarkRunner.Run<NopBenchmark>();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment