我刚刚建立了动态方法 - 见下文(感谢SO用户).似乎Func创建为动态方法,IL注入比lambda慢2倍.
谁知道为什么呢?
(编辑:这是在VS2010中作为Release 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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是输出(对于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...
Run Code Online (Sandbox Code Playgroud)
================================================== ==========================
编辑编辑编辑
这是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);
}
}
Run Code Online (Sandbox Code Playgroud)
usr*_*usr 13
常数5是原因.这究竟是为什么呢?原因:当JIT知道常量为5时,它不发出imul
指令而是发出一个指令[rax, rax * 4]
.这是众所周知的汇编级优化.但由于某种原因,此代码执行速度较慢.优化是一种悲观情绪.
发出闭包的C#编译器阻止了JIT以特定方式优化代码.
证明:将常量更改为56878567并且性能会发生变化.检查JITed代码时,您可以看到现在使用了imul.
我设法通过将常量5硬编码到lambda中来捕获这个:
static Func<int, int> EmbedConstFunc2(int b)
{
return a => a * 5;
}
Run Code Online (Sandbox Code Playgroud)
这让我可以检查JITed x86.
旁注:.NET JIT不以任何方式内联委托调用.只是提到这一点,因为错误地推测这是评论中的情况.
Sidenode 2:为了获得完整的JIT优化级别,您需要在Release模式下进行编译,并在没有附加调试器的情况下启动.即使在发布模式下,调试器也会阻止执行优化.
旁注3:虽然EmbedConstFunc包含一个闭包,并且通常比动态生成的方法慢,但这种"lea"优化的效果会造成更大的伤害并最终变慢.
lambda并不比DynamicMethod快.它基于.但是,静态方法比实例方法快,但静态方法的委托创建比委托创建实例方法慢.Lambda表达式构建一个静态方法,但通过将第一个paameter添加为"Closure"来使用它,就像实例方法一样.委托静态方法"pop"堆栈去除"mov"到真正的"IL body"之前不需要的"this"实例.例如,在委托方法的情况下,"IL body"被直接命中.这就是为什么通过lambda表达式构建一个hypotetic静态方法的委托更快(可能是实例/静态方法之间委托模式代码共享的副作用)
性能问题可通过将未使用的第一个参数(闭合型为例)DynamicMethod的被避免,并呼吁createDelegate方法有明确的目标实例(空可以使用).
var myDelegate = DynamicMethod.CreateDelegate(MyDelegateType,null)as MyDelegateType;
http://msdn.microsoft.com/fr-fr/library/z43fsh67(v=vs.110).aspx
托尼·汤
鉴于仅当在没有附加调试器的发布模式下运行时才存在性能差异,我能想到的唯一解释是 JIT 编译器能够对 lambda 表达式进行本机代码优化,而它无法对发出的表达式执行本机代码优化。 IL动态函数。
针对发布模式进行编译(打开优化)并在不附加调试器的情况下运行,lambda 始终比生成的 IL 动态方法快 2 倍。
使用附加到进程的调试器运行相同的发布模式优化构建,会使 lambda 性能与生成的 IL 动态方法相当或更差。
这两次运行之间的唯一区别在于 JIT 的行为。当调试进程时,JIT 编译器会抑制许多本机代码生成优化,以保留本机指令到 IL 指令到源代码行号的映射以及其他相关性,而这些相关性可能会被激进的本机指令优化所破坏。
仅当输入表达式图(在本例中为 IL 代码)匹配某些非常特定的模式和条件时,编译器才能应用特殊情况优化。JIT 编译器显然具有 lambda 表达式 IL 代码模式的特殊知识,并且为 lambda 生成与“正常”IL 代码不同的代码。
您的 IL 指令很可能与导致 JIT 编译器优化 lambda 表达式的模式不完全匹配。例如,IL 指令将 B 值编码为内联常量,而类似的 lambda 表达式从内部捕获的变量对象实例加载字段。即使生成的 IL 要模仿 C# 编译器生成的 lambda 表达式 IL 的捕获字段模式,它仍然可能不够“接近”,无法接受与 lambda 表达式相同的 JIT 处理。
正如评论中提到的,这很可能是由于内联 lambda 以消除调用/返回开销。如果是这种情况,我希望看到这种性能差异在更复杂的 lambda 表达式中消失,因为内联通常只为最简单的表达式保留。