Aquí en
este artículo, en los comentarios, no había nada de qué discutir, sino algo de "no convergencia" al comparar la velocidad de IL Emit y el árbol de expresión de Linq compilado.
Este mini artículo es un código de prueba de velocidad + los resultados de esta prueba.
Para la prueba, se seleccionó un código que se cruza con el contenido del artículo original: la serialización en una secuencia de longitud de una cadena, y luego de todos los bytes de una cadena codificada en UTF-16 (Encoding.Unicode).
El código de serialización en sí mismo puede no ser el más óptimo, pero cercano a eso si no usa construcciones inseguras.
El código en ambas implementaciones es el mismo, como puede ver al examinar la construcción de la expresión Lambda.
No me molesté con la generación de IL a través de Emit, el código que debería ser "óptimo". Solo escribí en C # en el método estático (de hecho, todos los métodos del programa de prueba son estáticos, porque esta es una aplicación de consola muy simple). El método se llama además
nativo .
El segundo método de comparación es la expresión generada por Lambda, compilada llamando al método Compilar (quizás se pueda lograr una ganancia de velocidad usando CompileToMethod, pero esto no es exacto): este método se denomina
Expresisons .
¡BONIFICACIÓN! Por alguna razón, se agregó una prueba adicional, una llamada indirecta al método utilizado en la prueba nativa , al asignar el método a un campo delegado estático; este método se llama Native Dlgt .Al comienzo de la aplicación, se genera la salida de ambos métodos, de modo que puede asegurarse de que el código genera exactamente los mismos datos.
Entonces, aquí está el código de la aplicación (todo a la vez):
Listadousing 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; } } }
Y aquí están los resultados de la prueba en la siguiente configuración:
CPU Intel (R) Core (TM) i7-3770 CPU @ 3.40GHz
Velocidad base: 3.90 GHz
Zócalos: 1
Corazones: 4
Procesadores lógicos: 8
Virtualización: habilitada
Caché L1: 256 KB
Caché L2: 1.0 MB
Caché L3: 8.0 MB
Utilización 8%
Velocidad 4.05 GHz
Tiempo de actividad 5: 00: 43: 01
Procesos 239
Subprocesos 4092
Tiradores 168774
Memoria 32.0 GB DDR3
Velocidad: 1600 MHz
Ranuras utilizadas: 4 de 4
Factor de forma: DIMM
Hardware reservado: 42.5 MB
Disponible 20.7 GB
20.1 GB en caché
Comprometido 13.4 / 36.7 GB
Pool paginado 855 MB
Grupo no paginado 442 MB
En uso (comprimido) 11,2 GB (48,6 MB)
Target .Net Framework 4.7
SO Windows 10 Pro x64 1803 compilación 17134.48
Entonces, los resultados prometidos:
Compilación en depuración, sin optimización, lanzamiento sin depurador (Ctrl + F5):
Prueba | Tiempo (intervalo de tiempo) | Tiempo (tic) | % tiempo |
---|
Nativa | 00: 00: 01.5760651 | 15760651 | 101,935% |
Dlgt nativo | 00: 00: 01.5461478 | 15461478 | 100% |
Expresiones | 00: 00: 01.5835454 | 15835454 | 102,4188% |
Compilación en Release, con optimización, inicio sin depurador (Ctrl + F5):
Prueba | Tiempo (intervalo de tiempo) | Tiempo (tic) | % tiempo |
---|
Nativa | 00: 00: 01.3182291
| 13182291
| 100% |
Dlgt nativo | 00: 00: 01.3300925
| 13300925
| 100.8999% |
Expresiones | 00: 00: 01.4871786
| 14871786
| 112,8164% |
Algunos resultados se pueden resumir:
- Compiled Expression Tree se ejecuta 1-2% más lento en Debug y 10-12 en Release, lo cual es muy bueno, considerando que generar código a través de Expression Tree en tiempo de ejecución es mucho más fácil.
- En modo de depuración, por alguna razón, una llamada de método indirecto a través de un delegado es más rápida que una llamada directa.
Bono número 2 debajo del spoiler - Depuración de la representación de expresiones Lambda antes de la compilación:
Lambda .Lambda # Lambda1 <System.Action`2 [System.IO.Stream, ExpressionSpeedTest.TestClass]> (
System.IO.Stream $ stream,
ExpressionSpeedTest.TestClass $ inst) {
Bloque (
System.Byte [] $ intBuff,
System.Byte [] $ strBuff) {
$ intBuff = .Call System.BitConverter.GetBytes (($ inst.StringProp) .Length);
$ strBuff = .Call (System.Text.Encoding.Unicode) .GetBytes ($ inst.StringProp);
.Call $ stream.Write (
$ intBuff,
0 0
$ intBuff.Length);
.Call $ stream.Write (
$ strBuff,
0 0
$ strBuff.Length)
}
}