为什么 lambda 比 IL 注入动态方法更快?

2024-04-26

我刚刚构建了动态方法 - 见下文(感谢其他 SO 用户)。看起来 Func 创建为动态方法,IL 注入比 lambda 慢 2 倍。

有谁知道具体原因吗?

(编辑:这是在 VS2010 中构建为 x64 版本的。请从控制台而不是从 Visual Studio F5 内部运行它。)

class Program
{
    static void Main(string[] args)
    {
        var mul1 = IL_EmbedConst(5);
        var res = mul1(4);

        Console.WriteLine(res);

        var mul2 = EmbedConstFunc(5);
        res = mul2(4);

        Console.WriteLine(res);

        double d, acc = 0;

        Stopwatch sw = new Stopwatch();

        for (int k = 0; k < 10; k++)
        {
            long time1;

            sw.Restart();

            for (int i = 0; i < 10000000; i++)
            {
                d = mul2(i);
                acc += d;
            }

            sw.Stop();

            time1 = sw.ElapsedMilliseconds;

            sw.Restart();

            for (int i = 0; i < 10000000; i++)
            {
                d = mul1(i);
                acc += d;
            }

            sw.Stop();

            Console.WriteLine("{0,6} {1,6}", time1, sw.ElapsedMilliseconds);
        }

        Console.WriteLine("\n{0}...\n", acc);
        Console.ReadLine();
    }

    static Func<int, int> IL_EmbedConst(int b)
    {
        var method = new DynamicMethod("EmbedConst", typeof(int), new[] { typeof(int) } );

        var il = method.GetILGenerator();

        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4, b);
        il.Emit(OpCodes.Mul);
        il.Emit(OpCodes.Ret);

        return (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>));
    }

    static Func<int, int> EmbedConstFunc(int b)
    {
        return a => a * b;
    }
}

这是输出(对于 i7 920)

20
20

25     51
25     51
24     51
24     51
24     51
25     51
25     51
25     51
24     51
24     51

4.9999995E+15...

=================================================== ==========================

编辑编辑编辑编辑编辑

这是证明dhtorpe是对的 - 更复杂的 lambda 将失去其优势。 证明这一点的代码(这证明 Lambda 具有exactly与 IL 注射具有相同的性能):

class Program
{
    static void Main(string[] args)
    {
        var mul1 = IL_EmbedConst(5);
        double res = mul1(4,6);

        Console.WriteLine(res);

        var mul2 = EmbedConstFunc(5);
        res = mul2(4,6);

        Console.WriteLine(res);

        double d, acc = 0;

        Stopwatch sw = new Stopwatch();

        for (int k = 0; k < 10; k++)
        {
            long time1;

            sw.Restart();

            for (int i = 0; i < 10000000; i++)
            {
                d = mul2(i, i+1);
                acc += d;
            }

            sw.Stop();

            time1 = sw.ElapsedMilliseconds;

            sw.Restart();

            for (int i = 0; i < 10000000; i++)
            {
                d = mul1(i, i + 1);
                acc += d;
            }

            sw.Stop();

            Console.WriteLine("{0,6} {1,6}", time1, sw.ElapsedMilliseconds);
        }

        Console.WriteLine("\n{0}...\n", acc);
        Console.ReadLine();
    }

    static Func<int, int, double> IL_EmbedConst(int b)
    {
        var method = new DynamicMethod("EmbedConstIL", typeof(double), new[] { typeof(int), typeof(int) });

        var log = typeof(Math).GetMethod("Log", new Type[] { typeof(double) });

        var il = method.GetILGenerator();

        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4, b);
        il.Emit(OpCodes.Mul);
        il.Emit(OpCodes.Conv_R8);

        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldc_I4, b);
        il.Emit(OpCodes.Mul);
        il.Emit(OpCodes.Conv_R8);

        il.Emit(OpCodes.Call, log);

        il.Emit(OpCodes.Sub);

        il.Emit(OpCodes.Ret);

        return (Func<int, int, double>)method.CreateDelegate(typeof(Func<int, int, double>));
    }

    static Func<int, int, double> EmbedConstFunc(int b)
    {
        return (a, z) => a * b - Math.Log(z * b);
    }
} 

原因是常数 5。到底为什么会这样?原因:当 JIT 知道常量为 5 时,它不会发出imul指令但有[rax, rax * 4]。这是众所周知的汇编级优化。但由于某种原因,这段代码执行速度较慢。优化是一种悲观。

C# 编译器发出一个闭包,阻止 JIT 以这种特定方式优化代码。

证明:将常数改为56878567,性能发生变化。检查 JIT 代码时,您可以看到现在使用了 imul。

我通过将常量 5 硬编码到 lambda 中来捕获这个问题,如下所示:

    static Func<int, int> EmbedConstFunc2(int b)
    {
        return a => a * 5;
    }

这使我能够检查 JITed x86。

旁注:.NET JIT 不会以任何方式内联委托调用。只是提到这一点,因为评论中错误地推测了这种情况。

Sidenode 2:为了获得完整的 JIT 优化级别,您需要在发布模式下编译并在不附加调试器的情况下启动。即使在发布模式下,调试器也会阻止执行优化。

旁注 3:虽然 EmbedConstFunc 包含一个闭包,并且通常会比动态生成的方法慢,但这种“lea”优化的效果会造成更大的损害,最终会更慢。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 lambda 比 IL 注入动态方法更快? 的相关文章

随机推荐