Ici, dans
cet article dans les commentaires, il n'y avait pas qu'un différend, mais une certaine "non convergence" dans la comparaison de la vitesse d'IL Emit et de l'arbre d'expression Linq compilé.
Ce mini article est le code du test de vitesse + les résultats de l'exécution de ce test.
Pour le test, un code a été sélectionné qui intersecte le contenu de l'article d'origine - sérialisation en un flux de longueur d'une chaîne, puis de tous les octets d'une chaîne codée en UTF-16 (Encoding.Unicode).
Le code de sérialisation lui-même n'est peut-être pas le plus optimal, mais proche de cela si vous n'utilisez pas de constructions dangereuses.
Le code dans les deux implémentations est le même, comme vous pouvez le voir en examinant la construction de l'expression Lambda.
Je ne me suis pas soucié de la génération d'IL via Emit - le code qui devrait être "optimal" que je viens d'écrire en C # dans la méthode statique (en fait, toutes les méthodes du programme de test sont statiques, car il s'agit d'une application console très simple) - ce la méthode est en outre appelée
Native .
La deuxième méthode de comparaison est l'expression générée par Lambda, compilée en appelant la méthode Compile (peut-être qu'un gain de vitesse peut être obtenu en utilisant CompileToMethod, mais ce n'est pas précis) - cette méthode est appelée aussi
Expresisons .
BONUS! Pour une raison quelconque, un test supplémentaire a été ajouté - un appel indirect à la méthode utilisée dans le test natif - en affectant la méthode à un champ délégué statique - cette méthode est appelée Native Dlgt .Au tout début de l'application, la sortie des deux méthodes est sortie, afin que vous puissiez vous assurer que le code génère exactement les mêmes données.
Voici donc le code de l'application (d'un seul coup):
Annonceusing System; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace ExpressionSpeedTest { class Program { static void Main(string[] args) { InitExpression(); InitDelegateNative(); var inst = new TestClass { StringProp = "abcdefabcdef" }; byte[] buff1, buff2; using (var ms1 = new MemoryStream()) { SaveString(ms1, inst); buff1 = ms1.ToArray(); } using (var ms2 = new MemoryStream()) { DynamicMethod(ms2, inst); buff2 = ms2.ToArray(); } Console.WriteLine($"Native string: {string.Join("", buff1.Select(b => Encoding.Default.GetString(new[] { b })))}"); Console.WriteLine($"Expressions string: {string.Join("", buff2.Select(b => Encoding.Default.GetString(new[] { b })))}"); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestDelegateNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestExpressions(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); Console.ReadLine(); } private static void TestDelegateNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native Dlgt test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void InitDelegateNative() { NativeDelegate = SaveString; } private static void InitExpression() { var intGetBytes = typeof(BitConverter).GetMethods(BindingFlags.Static | BindingFlags.Public) .Single(x => x.Name == nameof(BitConverter.GetBytes) && x.GetParameters()[0].ParameterType == typeof(int)); var stringGetBytes = typeof(Encoding).GetMethods(BindingFlags.Instance | BindingFlags.Public) .Single(x => x.Name == nameof(Encoding.GetBytes) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(string)); var unicodeProp = typeof(Encoding).GetProperty(nameof(Encoding.Unicode), BindingFlags.Static | BindingFlags.Public); var streamWrite = typeof(Stream).GetMethod(nameof(Stream.Write)); var streamPar = Expression.Parameter(typeof(Stream), "stream"); var instPar = Expression.Parameter(typeof(TestClass), "inst"); var intBuffVar = Expression.Variable(typeof(byte[]), "intBuff"); var strBuffVar = Expression.Variable(typeof(byte[]), "strBuff"); var expressionBody = Expression.Block( new[] { intBuffVar, strBuffVar }, Expression.Assign(intBuffVar, Expression.Call(null, intGetBytes, Expression.Property( Expression.Property( instPar, nameof(TestClass.StringProp)), nameof(string.Length)))), Expression.Assign(strBuffVar, Expression.Call(Expression.Property(null, unicodeProp), stringGetBytes, Expression.Property( instPar, nameof(TestClass.StringProp) ))), Expression.Call(streamPar, streamWrite, intBuffVar, Expression.Constant(0), Expression.Property(intBuffVar, nameof(Array.Length))), Expression.Call(streamPar, streamWrite, strBuffVar, Expression.Constant(0), Expression.Property(strBuffVar, nameof(Array.Length))) ); DynamicMethod = Expression.Lambda<Action<Stream, TestClass>>(expressionBody, streamPar, instPar).Compile(); } private const int loopLength = 10000000; private static Action<Stream, TestClass> DynamicMethod; private static Action<Stream, TestClass> NativeDelegate; private static void TestExpressions() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { DynamicMethod(ms, inst); } sw.Stop(); Console.WriteLine($"Expressions test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void TestNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } public static void SaveString(Stream stream, TestClass instance) { var intBuff = BitConverter.GetBytes(instance.StringProp.Length); var strBuff = Encoding.Unicode.GetBytes(instance.StringProp); stream.Write(intBuff, 0, intBuff.Length); stream.Write(strBuff, 0, strBuff.Length); } } class TestClass { public string StringProp { get; set; } } }
Et voici les résultats des tests sur la configuration suivante:
CPU Processeur Intel (R) Core (TM) i7-3770 à 3,40 GHz
Vitesse de base: 3,90 GHz
Prises: 1
Noyaux: 4
Processeurs logiques: 8
Virtualisation: activée
Cache L1: 256 Ko
Cache L2: 1,0 Mo
Cache L3: 8,0 Mo
Utilisation 8%
Vitesse 4,05 GHz
Temps de disponibilité 5: 00: 43: 01
Processus 239
Fils 4092
Poignées 168774
La mémoire 32,0 Go de DDR3
Vitesse: 1600 MHz
Emplacements utilisés: 4 sur 4
Facteur de forme: DIMM
Matériel réservé: 42,5 Mo
Disponible 20,7 Go
En cache 20,1 Go
Engagé 13,4 / 36,7 Go
Pool paginé 855 Mo
Pool non paginé 442 Mo
En cours d'utilisation (compressé) 11,2 Go (48,6 Mo)
Target .Net Framework 4.7
OS Windows 10 Pro x64 1803 build 17134.48
Donc, les résultats promis:
Compilation dans Debug, sans optimisation, lancement sans débogueur (Ctrl + F5):
Test | Heure (durée) | Heure (ticks) | % temps |
---|
Natif | 00: 00: 01.5760651 | 15760651 | 101,935% |
DLG natif | 00: 00: 01.5461478 | 15461478 | 100% |
Expressions | 00: 00: 01.5835454 | 15835454 | 102,4188% |
Compilation en Release, avec optimisation, démarrage sans débogueur (Ctrl + F5):
Test | Heure (durée) | Heure (ticks) | % temps |
---|
Natif | 00: 00: 01.3182291
| 13182291
| 100% |
DLG natif | 00: 00: 01.3300925
| 13300925
| 100,8999% |
Expressions | 00: 00: 01.4871786
| 14871786
| 112,8164% |
Vous pouvez résumer certains des résultats:
- L'arbre d'expression compilé s'exécute 1 à 2% plus lentement dans le débogage et 10 à 12 dans la version, ce qui est très bien, étant donné que la génération de code via l'arbre d'expression en cours d'exécution est beaucoup plus facile.
- En mode débogage, pour une raison quelconque, un appel de méthode indirecte via un délégué est plus rapide qu'un appel direct.
Bonus numéro 2 sous le spoiler - Représentation du débogage des expressions Lambda avant la compilation:
Lambda .Lambda # Lambda1 <System.Action`2 [System.IO.Stream, ExpressionSpeedTest.TestClass]> (
System.IO.Stream $ stream,
ExpressionSpeedTest.TestClass $ inst) {
.Block (
System.Byte [] $ intBuff,
System.Byte [] $ strBuff) {
$ intBuff = .Call System.BitConverter.GetBytes (($ inst.StringProp) .Length);
$ strBuff = .Call (System.Text.Encoding.Unicode) .GetBytes ($ inst.StringProp);
.Appelez $ stream.Write (
$ intBuff,
0
$ intBuff.Length);
.Appelez $ stream.Write (
$ strBuff,
0
$ strBuff.Length)
}
}