3

以前、C# でコードを動的にコンパイルすることについてこの質問を投稿しましたが、その回答が別の質問につながりました。

1 つの提案は、私が試したデリゲートを使用することであり、うまく機能します。ただし、ベンチングは直接呼び出しよりも約 8.4 倍遅く、意味がありません。

このコードの何が問題になっていますか?

私の結果、.Net 4.0、64ビット、exeを直接実行しました:62、514、530

public static int Execute(int i) { return i * 2; }

private void button30_Click(object sender, EventArgs e)
{
    CSharpCodeProvider foo = new CSharpCodeProvider();

    var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters()
        {
            GenerateInMemory = true,
            CompilerOptions = @"/optimize",                    
        },
        @"public class FooClass { public static int Execute(int i) { return i * 2; }}"
    );

    var type = res.CompiledAssembly.GetType("FooClass");
    var obj = Activator.CreateInstance(type);
    var method = type.GetMethod("Execute");
    int i = 0, t1 = Environment.TickCount, t2;
    //var input = new object[] { 2 };

    //for (int j = 0; j < 10000000; j++)
    //{
    //    input[0] = j;
    //    var output = method.Invoke(obj, input);
    //    i = (int)output;
    //}

    //t2 = Environment.TickCount;

    //MessageBox.Show((t2 - t1).ToString() + Environment.NewLine + i.ToString());

    t1 = Environment.TickCount;

    for (int j = 0; j < 100000000; j++)
    {
        i = Execute(j);
    }

    t2 = Environment.TickCount;

    MessageBox.Show("Native: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());

    var func = (Func<int, int>) Delegate.CreateDelegate(typeof (Func<int, int>), method);

    t1 = Environment.TickCount;

    for (int j = 0; j < 100000000; j++)
    {
        i = func(j);
    }

    t2 = Environment.TickCount;

    MessageBox.Show("Dynamic delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());

    Func<int, int> funcL = Execute;

    t1 = Environment.TickCount;

    for (int j = 0; j < 100000000; j++)
    {
        i = funcL(j);
    }

    t2 = Environment.TickCount;

    MessageBox.Show("Delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
}
4

2 に答える 2

5

ハンスがあなたの質問のコメントで言及しているように、このExecute方法は非常に単純であるため、「ネイティブ」テストのジッターによってほぼ確実にインライン化されています。

したがって、ここに表示されているのは、標準メソッド呼び出しとデリゲート呼び出しの比較ではなく、インラインi * 2操作とデリゲート呼び出しの比較です。(そして、そのi * 2操作はおそらく、可能な限り高速な単一の機械語命令に要約されます。)

メソッドExecuteをもう少し複雑にして、インライン化を防ぎます (および/またはMethodImplOptions.NoInliningコンパイラ ヒントを使用して実行します)。そうすれば、標準のメソッド呼び出しとデリゲート呼び出しをより現実的に比較できます。ほとんどの状況では、違いは無視できる可能性があります。

[MethodImpl(MethodImplOptions.NoInlining)]
static int Execute(int i) { return ((i / 63.53) == 34.23) ? -1 : (i * 2); }
public static volatile int Result;

private static void Main(string[] args)
{
    const int iterations = 100000000;

    {
        Result = Execute(42);  // pre-jit
        var s = Stopwatch.StartNew();

        for (int i = 0; i < iterations; i++)
        {
            Result = Execute(i);
        }
        s.Stop();
        Console.WriteLine("Native: " + s.ElapsedMilliseconds);
    }

    {
        Func<int, int> func;
        using (var cscp = new CSharpCodeProvider())
        {
            var cp = new CompilerParameters { GenerateInMemory = true, CompilerOptions = @"/optimize" };
            string src = @"public static class Foo { public static int Execute(int i) { return ((i / 63.53) == 34.23) ? -1 : (i * 2); } }";

            var cr = cscp.CompileAssemblyFromSource(cp, src);
            var mi = cr.CompiledAssembly.GetType("Foo").GetMethod("Execute");
            func = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), mi);
        }

        Result = func(42);  // pre-jit
        var s = Stopwatch.StartNew();

        for (int i = 0; i < iterations; i++)
        {
            Result = func(i);
        }
        s.Stop();
        Console.WriteLine("Dynamic delegate: " + s.ElapsedMilliseconds);
    }

    {
        Func<int, int> func = Execute;
        Result = func(42);  // pre-jit

        var s = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            Result = func(i);
        }
        s.Stop();
        Console.WriteLine("Delegate: " + s.ElapsedMilliseconds);
    }
}
于 2012-05-16T16:57:28.350 に答える
4

それは理にかなっている。デリゲートは関数ポインタではありません。それらは、型チェック、セキュリティ、および他の多くのものを意味します。パフォーマンスへの影響がまったく異なるものに由来する場合でも、仮想関数呼び出しの速度に近くなります(この投稿を参照)。

さまざまな呼び出し手法(質問で言及されていないものもあります)の適切な比較については、この記事をお読みください。

于 2012-05-16T11:47:00.510 に答える