2012-06-13 15 views
15

我刚刚构建了动态方法 - 请参阅下文(感谢SO用户)。看来,Func创建为IL注入比lambda慢2倍的动态方法。为什么lambda比IL注入动态方法更快?

任何人都知道为什么呢?

(编辑:这是在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... 

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

EDIT EDIT编辑编辑

这里是证明dhtorpe是正确的 - 更复杂的lambda将失去其优势。 代码以证明它(这表明,LAMBDA具有恰好与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); 
    } 
} 
+2

不能说我知道,但是这取决于你使用这个东西你可能想看看在自由Fasterflect API:HTTP://fasterflect.codeplex。 com/ –

+1

我在这里对你的代码的非正式测试中没有看到 - mul1(IL版本)在这里一直更快(大约2倍)。 –

+0

我添加了输出到问题。 mul2快两倍。请在x64 Release中构建,然后从控制台运行。不是从Visual Studio里面。 –

回答

2

鉴于只有在发布模式下运行时没有附加调试器才会出现性能差异,我能想到的唯一解释是JIT编译器能够为lambda表达式进行本地代码优化,使其无法执行发射的IL动态功能。

对于发布模式(优化开启)进行编译并且在没有附加调试器的情况下运行,lambda始终比生成的IL动态方法快2倍。

使用与进程相连的调试器运行相同的发布模式优化版本会将lambda表现降至与生成的IL动态方法相当或更差的水平。

这两次运行的唯一区别在于JIT的行为。当一个进程正在被调试时,JIT编译器会抑制一些本地代码gen优化,以保留对IL指令的本地指令,以保留源代码行号映射和其他相关性,这些相关性会被侵略性的本机指令优化所破坏。

编译器只能在输入表达式图形(在本例中为IL代码)匹配某些非常特定的模式和条件时应用特殊情况优化。 JIT编译器明确具有对lambda表达式IL代码模式的特殊知识,并且对于lambda表达式发出的代码不同于“正常”IL代码。

你的IL指令很可能并不完全匹配导致JIT编译器优化lambda表达式的模式。例如,您的IL指令将B值编码为内联常量,而类似的lambda表达式则从内部捕获的变量对象实例中加载一个字段。即使您生成的IL模仿C#编译器生成的lambda表达式IL捕获的字段模式,它仍然可能不够“足够接近”以接收与lambda表达式相同的JIT处理。

正如在评论中提到的,这可能是由于lambda的内联消除了调用/返回开销。如果是这种情况,我期望看到这种性能差异在更复杂的lambda表达式中消失,因为内联通常仅用于最简单的表达式。

11

恒定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; 
    } 

这让我检查JIT编译的x86。

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

Sidenode 2:为了获得完整的JIT优化级别,您需要在Release模式下进行编译,并且在没有附加调试器的情况下启动。调试器阻止执行优化,即使在发布模式下也是如此。旁注3:尽管EmbedConstFunc包含一个闭包,通常会比动态生成的方法慢,但这种“优化”优化的效果会造成更多的破坏,最终会变慢。

+0

+1:不错的地方:)(关闭) – leppie

+1

你正在向后看。生成的方法比lambda表达式慢2倍。您的参数支持生成的方法比lambda表达式更快。 – dthorpe

+0

好吧,好像我倒过来了。我为x86和x64重申了这一点。我检查了IL代码,我所说的* *看起来都是真的。然而,这些数字并不是谎言!嗯...... Visual Studio不允许我调试EmbedConst func的JITed代码。 – usr

4

lambda不比DynamicMethod快。它基于。但是,静态方法比实例方法更快,但对于静态方法的委托创建比委托创建实例方法要慢。 Lambda表达式构建一个静态方法,但像实例方法一样使用它作为第一个paameter添加一个“Closure”。委托静态方法“流行”堆栈摆脱“mov”之前不需要的“this”实例到真正的“IL主体”。例如在委托的情况下直接命中“IL身体”的方法。这就是为什么通过lambda表达式构建一个直观的静态方法的委托会更快(也许是委托模式代码在实例/静态方法之间共享的副作用)

性能问题可以通过添加一个未使用的第一个参数例如闭包类型)转换为DynamicMethod并用显式目标实例调用CreateDelegate(可以使用null)。

var myDelegate = DynamicMethod.CreateDelegate(MyDelegateType,null)as MyDelegateType;

http://msdn.microsoft.com/fr-fr/library/z43fsh67(v=vs.110).aspx

托尼THONG

相关问题