Performance des arbres d'expression

.net c# expression-trees performance

Question

Ma compréhension actuelle est ce code "codé en dur" comme ceci:

public int Add(int x, int y) {return x + y;}

fonctionnera toujours mieux que le code d’arbre d’expression comme ceci:

Expression<Func<int, int, int>> add = (x, y) => x + y; 
var result = add.Compile()(2, 3);

var x = Expression.Parameter(typeof(int)); 
var y = Expression.Parameter(typeof(int)); 
return (Expression.Lambda(Expression.Add(x, y), x, y).
    Compile() as Func<int, int, int>)(2, 3);

en tant que compilateur a plus d'informations et peut consacrer plus d'effort à l'optimisation du code si vous le compilez au moment de la compilation. Est-ce généralement vrai?

Réponse acceptée

Compilation

L'appel à Expression.Compile suit exactement le même processus que tout autre code .NET que votre application contient, en ce sens que:

  • Le code IL est généré
  • Le code IL est conforme au code machine

(l'étape d'analyse est ignorée car une arborescence d'expression est déjà créée et ne doit pas être générée à partir du code d'entrée)

Vous pouvez consulter le code source du compilateur d'expression pour vérifier que le code IL est bien généré.

Optimisation

Veuillez noter que la quasi-totalité de l'optimisation effectuée par le CLR est effectuée dans l'étape JIT, et non à partir de la compilation du code source C #. Cette optimisation sera également effectuée lors de la compilation du code IL de votre délégué lambda en code machine.

Votre exemple

Dans votre exemple, vous comparez des pommes et des oranges. Le premier exemple est une définition de méthode, le second exemple est un code d'exécution qui crée une méthode, la compile et l'exécute. Le temps nécessaire pour créer / compiler la méthode est beaucoup plus long que son exécution réelle. Cependant, vous pouvez conserver une instance de la méthode compilée après la création. Ceci fait, les performances de votre méthode générée doivent être identiques à celles de la méthode C # d'origine.

Considérons ce cas:

private static int AddMethod(int a, int b)
{
    return a + b;
}

Func<int, int, int> add1 = (a, b) => a + b;
Func<int, int, int> add2 = AddMethod;

var x = Expression.Parameter(typeof (int));
var y = Expression.Parameter(typeof (int));
var additionExpr = Expression.Add(x, y);
Func<int, int, int> add3 = 
              Expression.Lambda<Func<int, int, int>>(
                  additionExpr, x, y).Compile();
//the above steps cost a lot of time, relatively.

//performance of these three should be identical
add1(1, 2);
add2(1, 2);
add3(1, 2);

La conclusion à tirer est donc la suivante: le code IL est le code IL, quelle que soit la façon dont il est généré, et Linq Expressions génère le code IL.


Réponse populaire

Votre fonction Add compile probablement une surcharge de la fonction (si elle n'est pas en ligne) et une seule instruction add. Ça ne va pas plus vite que ça.

Même en construisant cet arbre d'expression, les ordres de grandeur vont être plus lents. Compiler une nouvelle fonction pour chaque appel va coûter incroyablement cher comparé à l'implémentation directe en C #.

Essayez de compiler la fonction une seule fois et de la stocker quelque part.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow