为什么从Expression <Func <>>创建的Func <>比直接声明的Func <>慢?

Mar*_*inF 24 c# delegates expression func expression-trees

为什么Func<>从Expression<Func<>>via .Compile()创建的文件比直接使用Func<>声明要慢得多?

我刚刚使用Func<IInterface, object>声明直接更改为Expression<Func<IInterface, object>>在我正在处理的应用程序中创建的一个,我注意到性能下降了.

我刚做了一点测试,Func<>从一个Expression创建的"几乎"是Func<>直接声明的时间的两倍.

在我的机器上,Direct Func<>大约需要7.5秒,Expression<Func<>>大约需要12.6秒.

这是我使用的测试代码(运行Net 4.0)

// Direct
Func<int, Foo> test1 = x => new Foo(x * 2);

int counter1 = 0;

Stopwatch s1 = new Stopwatch();
s1.Start();
for (int i = 0; i < 300000000; i++)
{
 counter1 += test1(i).Value;
}
s1.Stop();
var result1 = s1.Elapsed;



// Expression . Compile()
Expression<Func<int, Foo>> expression = x => new Foo(x * 2);
Func<int, Foo> test2 = expression.Compile();

int counter2 = 0;

Stopwatch s2 = new Stopwatch();
s2.Start();
for (int i = 0; i < 300000000; i++)
{
 counter2 += test2(i).Value;
}
s2.Stop();
var result2 = s2.Elapsed;



public class Foo
{
 public Foo(int i)
 {
  Value = i;
 }
 public int Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能恢复性能?

有什么我可以做的,以便Func<>从Expression<Func<>>直接声明的执行创建?

Gab*_*abe 19

正如其他人所提到的,调用动态委托的开销导致您的速度减慢.在我的电脑上,我的CPU处于3GHz,开销约为12ns.解决这个问题的方法是从已编译的程序集加载方法,如下所示:

var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
             new AssemblyName("assembly"), AssemblyBuilderAccess.Run);
var mod = ab.DefineDynamicModule("module");
var tb = mod.DefineType("type", TypeAttributes.Public);
var mb = tb.DefineMethod(
             "test3", MethodAttributes.Public | MethodAttributes.Static);
expression.CompileToMethod(mb);
var t = tb.CreateType();
var test3 = (Func<int, Foo>)Delegate.CreateDelegate(
                typeof(Func<int, Foo>), t.GetMethod("test3"));

int counter3 = 0;
Stopwatch s3 = new Stopwatch();
s3.Start();
for (int i = 0; i < 300000000; i++)
{
    counter3 += test3(i).Value;
}
s3.Stop();
var result3 = s3.Elapsed;
Run Code Online (Sandbox Code Playgroud)

当我添加上面的代码时,result3总是只有一秒的高度result1,大约1ns的开销.

那么为什么test2当你可以有一个更快的委托(test3)时,甚至打扰编译的lambda ()?因为创建动态程序集通常会产生更多的开销,并且每次调用时只会节省10-20ns.

  • 非常好.我用扩展方法快速包裹了这个,然后我恢复了"速度"(增加了大约30-40%)谢谢!:) (2认同)

cdh*_*wie 6

(这不是一个正确的答案,但是有助于发现答案的材料.)

从Mono 2.6.7收集的统计数据 - Debian Lenny - Linux 2.6.26 i686 - 2.80GHz单核:

      Func: 00:00:23.6062578
Expression: 00:00:23.9766248
Run Code Online (Sandbox Code Playgroud)

因此,在Mono上,至少两种机制似乎都会产生等效的IL.

这是由Mono gmcs为匿名方法生成的IL :

// method line 6
.method private static  hidebysig
       default class Foo '<Main>m__0' (int32 x)  cil managed
{
    .custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() =  (01 00 00 00 ) // ....

    // Method begins at RVA 0x2204
    // Code size 9 (0x9)
    .maxstack 8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.2
    IL_0002:  mul
    IL_0003:  newobj instance void class Foo::'.ctor'(int32)
    IL_0008:  ret
} // end of method Default::<Main>m__0
Run Code Online (Sandbox Code Playgroud)

我将致力于提取表达式编译器生成的IL.