Je suis un grand fan de la tendance relativement récente d'utiliser des expressions lambda au lieu de chaînes pour indiquer des propriétés dans, par exemple, le mappage ORM. Fortement typé >>>> Stringly typé.Suggestions pour optimiser les expressions de passage en tant que paramètres de méthode
Pour être clair, voici ce dont je parle:
builder.Entity<WebserviceAccount>()
.HasTableName("webservice_accounts")
.HasPrimaryKey(_ => _.Id)
.Property(_ => _.Id).HasColumnName("id")
.Property(_ => _.Username).HasColumnName("Username").HasLength(255)
.Property(_ => _.Password).HasColumnName("Password").HasLength(255)
.Property(_ => _.Active).HasColumnName("Active");
Dans certains travaux récents, je l'ai fait, j'ai besoin de trucs de mise en cache basée sur l'expression et de le faire, J'avais besoin de créer une clé basée sur l'expression. Comme ceci:
static string GetExprKey(Expression<Func<Bar,int>> expr)
{
string key = "";
Expression e = expr.Body;
while(e.NodeType == ExpressionType.MemberAccess)
{
var me = (MemberExpression)e;
key += "<" + (me.Member as PropertyInfo).Name;
e = me.Expression;
}
key += ":" + ((ParameterExpression)e).Type.Name;
return key;
}
Remarques: La version de StringBuilder est presque identique. Il est seulement supposé fonctionner pour les expressions qui ont la forme x => x.A.B.C
, toute autre chose est une erreur et devrait échouer. Oui, je dois mettre en cache. Non, la compilation est beaucoup plus lente que la génération/comparaison de clés dans mon cas. En comparant diverses fonctions de keygen, j'ai été mystifié de découvrir qu'elles se comportaient toutes horriblement.
Même la version fictive qui vient de retourner ""
. Après quelques tentatives, j'ai découvert que c'était vraiment l'instanciation de l'objet Expression qui était super chère.
Voici la sortie de la nouvelle référence que j'ai créé pour mesurer cet effet:
Dummy(_ => _.F.Val) 4106,5036 ms, 0,0041065036 ms/iter
Dummy(cachedExpr) 0,3599 ms, 3,599E-07 ms/iter
Dummy(Bar_Foo_Val ?? (Bar_Foo_Val = _ => _.F.Val)) 2,3127 ms, 2,3127E-06 ms/iter
Et voici le code de la référence:
using System;
using System.Diagnostics;
using System.Linq.Expressions;
namespace ExprBench
{
sealed class Foo
{
public int Val { get; set; }
}
sealed class Bar
{
public Foo F { get; set; }
}
public static class ExprBench
{
static string Dummy(Expression<Func<Bar, int>> expr)
{
return "";
}
static Expression<Func<Bar, int>> Bar_Foo_Val;
static public void Run()
{
var sw = Stopwatch.StartNew();
TimeSpan elapsed;
int iterationCount = 1000000;
sw.Restart();
for(int j = 0; j<iterationCount; ++j)
Dummy(_ => _.F.Val);
elapsed = sw.Elapsed;
Console.WriteLine($"Dummy(_ => _.F.Val) {elapsed.TotalMilliseconds} ms, {elapsed.TotalMilliseconds/iterationCount} ms/iter");
Expression<Func<Bar, int>> cachedExpr = _ => _.F.Val;
sw.Restart();
for(int j = 0; j<iterationCount; ++j)
Dummy(cachedExpr);
elapsed = sw.Elapsed;
Console.WriteLine($"Dummy(cachedExpr) {elapsed.TotalMilliseconds} ms, {elapsed.TotalMilliseconds/iterationCount} ms/iter");
sw.Restart();
for(int j = 0; j<iterationCount; ++j)
Dummy(Bar_Foo_Val ?? (Bar_Foo_Val = _ => _.F.Val));
elapsed = sw.Elapsed;
Console.WriteLine($"Dummy(Bar_Foo_Val ?? (Bar_Foo_Val = _ => _.F.Val)) {elapsed.TotalMilliseconds} ms, {elapsed.TotalMilliseconds/iterationCount} ms/iter");
}
}
}
Cela démontre clairement que speedup 2000 -10000 fois peut être atteint avec une mise en cache simple.
Le problème est, que ces solutions de contournement, à divers degrés, compromettent la beauté et la sécurité de l'utilisation des expressions de cette manière.
La deuxième solution de contournement au moins maintient la ligne d'expression, mais il est loin d'être assez,
Ainsi, les questions se sont-il d'autres solutions de contournement que je pourrais avoir manqué, qui sont moins laids?
Merci à l'avance
Eh bien, la création d'un 'Expression' d'une expression lambda n'est pas libre, est toujours va être apreciably plus cher que d'avoir la' Expression' pour commencer. La solution est évidemment, comme vous le savez déjà, la mise en cache. Il n'y a aucun moyen de contourner cela à moins de pouvoir créer un analyseur d'expression lambda plus rapide ... – InBetween
Peut-être existe-t-il un moyen de spécifier des expressions lambda pour que le compilateur puisse optimiser et réutiliser la même instance? Je suis à la recherche de solutions de rechange alternatives. S'il n'y en a pas, alors ça va et je vais simplement utiliser la mise en cache sur les chemins d'accès à chaud. En C++, je regarderais les statistiques et les macros locales en ce moment. – JJJ
Intéressant. Les délégués lambda statiques (comme 'Func <...>' etc.) sont mis en cache, je considérais automatiquement la même chose pour 'Expression>', mais vous avez raison, ils sont non seulement instanciés, mais aussi construits avec des méthodes 'Expression' à tout moment. appel. On dirait qu'il n'y a pas d'autre moyen actuellement de les mettre en cache dans des champs statiques en lecture seule. –