Pourquoi Func &lt;&gt; est-il créé à partir d&#39;Expression <Func<> &gt; plus lent que Func &lt;&gt; déclaré directement?

c# delegates expression expression-trees func

Question

Pourquoi un Func<> créé à partir d'une Expression<Func<>> via .Compile () est-il beaucoup plus lent que le simple fait d'utiliser un Func<> déclaré directement?

Je viens de Func<IInterface, object> utilisation d'un Func<IInterface, object> déclaré directement en un créé à partir d'un Expression<Func<IInterface, object>> dans une application sur laquelle je travaille et j'ai remarqué que la performance avait diminué.

Je viens de faire un petit test, et le Func<> créé à partir d'une expression prend "presque" le double du temps d'un Func<> déclaré directement.

Sur ma machine, le Direct Func<> prend environ 7,5 secondes et l’ Expression<Func<>> prend environ 12,6 secondes.

Voici le code de test que j'ai utilisé (avec 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; }
}

Comment puis-je récupérer la performance?

Y a-t-il quelque chose que je puisse faire pour que le Func<> créé à partir de l' Expression<Func<>> exécuté comme celui déclaré directement?

Réponse acceptée

Comme d’autres l’ont mentionné, l’appel d’un délégué dynamique entraîne un ralentissement de votre travail. Sur mon ordinateur, cette surcharge est d'environ 12 ns avec mon processeur à 3 GHz. La solution consiste à charger la méthode à partir d'un assembly compilé, comme ceci:

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;

Lorsque j'ajoute le code ci-dessus, result3 est toujours juste une fraction de seconde plus élevé que result1 , pour une surcharge d'environ result1 .

Alors pourquoi même s’embêter avec un lambda compilé ( test2 ) alors que vous pouvez avoir un délégué plus rapide ( test3 )? Parce que créer un assemblage dynamique est généralement beaucoup plus fastidieux et ne vous fait économiser que 10 à 20ns à chaque appel.


Réponse populaire

(Ce n'est pas une bonne réponse, mais c'est un matériel destiné à aider à découvrir la réponse.)

Statistiques recueillies à partir de Mono 2.6.7 - Debian Lenny - Linux 2.6.26 Monochrome i686 - 2,80 GHz:

      Func: 00:00:23.6062578
Expression: 00:00:23.9766248

Donc, sur Mono, au moins les deux mécanismes semblent générer une IL équivalente.

C'est l'IL généré par les gmcs de Mono pour la méthode anonyme:

      Func: 00:00:23.6062578
Expression: 00:00:23.9766248

Je vais travailler sur l'extraction de l'IL généré par le compilateur d'expression.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi