Velocidad de árbol de expresión de Linq compilada

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):

Listado
using 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):
PruebaTiempo (intervalo de tiempo)Tiempo (tic)% tiempo
Nativa00: 00: 01.576065115760651101,935%
Dlgt nativo00: 00: 01.546147815461478100%
Expresiones00: 00: 01.583545415835454102,4188%

Compilación en Release, con optimización, inicio sin depurador (Ctrl + F5):
PruebaTiempo (intervalo de tiempo)Tiempo (tic)% tiempo
Nativa00: 00: 01.3182291
13182291
100%
Dlgt nativo00: 00: 01.3300925
13300925
100.8999%
Expresiones00: 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)
     }
 }

Source: https://habr.com/ru/post/es413561/


All Articles