referencia por pase pasar parametros objetos funciones como comando argumentos c# linq lambda expression-trees

por - Rendimiento de expresión lambda C#compilado con imbricación



pase de parametros por referencia c# (2)

  1. La razón principal por la que el lambda compilado es más lento es porque el delegado se crea una y otra vez. Los delegados anónimos son una raza especial: solo se usan en un lugar. Por lo tanto, el compilador puede realizar algunas optimizaciones especiales, como almacenar en caché el valor la primera vez que se llama al delegado. Esto es lo que está sucediendo aquí.

  2. No pude reproducir la gran diferencia entre la llamada directa y la llamada lambda. De hecho, en mis mediciones, la llamada directa es un poco más rápida.

Al hacer puntos de referencia como este, es posible que desee utilizar un temporizador más preciso. La clase Cronómetro en System.Diagnostics es ideal. También puede querer aumentar su número de iteraciones. El código solo se ejecuta durante unos pocos milisegundos.

Además, el primero de los tres casos incurrirá en una ligera sobrecarga de JIT en la clase Parser . Intente ejecutar el primer caso dos veces y vea qué sucede. O mejor aún: use el número de iteraciones como un parámetro en cada método, y llame a cada método primero para 1 iteración, para que todos comiencen en un campo de juego nivelado.

Considerando esta clase:

/// <summary> /// Dummy implementation of a parser for the purpose of the test /// </summary> class Parser { public List<T> ReadList<T>(Func<T> readFunctor) { return Enumerable.Range(0, 10).Select(i => readFunctor()).ToList(); } public int ReadInt32() { return 12; } public string ReadString() { return "string"; } }

Intento generar la siguiente llamada con un árbol de expresiones lambda compilado:

Parser parser = new Parser(); List<int> list = parser.ReadList(parser.ReadInt32);

Sin embargo, el rendimiento no es el mismo ...

class Program { private const int MAX = 1000000; static void Main(string[] args) { DirectCall(); LambdaCall(); CompiledLambdaCall(); } static void DirectCall() { Parser parser = new Parser(); var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < MAX; i++) { List<int> list = parser.ReadList(parser.ReadInt32); } sw.Stop(); Console.WriteLine("Direct call: {0} ms", sw.ElapsedMilliseconds); } static void LambdaCall() { Parser parser = new Parser(); var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < MAX; i++) { List<int> list = parser.ReadList(() => parser.ReadInt32()); } sw.Stop(); Console.WriteLine("Lambda call: {0} ms", sw.ElapsedMilliseconds); } static void CompiledLambdaCall() { var parserParameter = Expression.Parameter(typeof(Parser), "parser"); var lambda = Expression.Lambda<Func<Parser, List<int>>>( Expression.Call( parserParameter, typeof(Parser).GetMethod("ReadList").MakeGenericMethod(typeof(int)), Expression.Lambda( typeof(Func<int>), Expression.Call( parserParameter, typeof(Parser).GetMethod("ReadInt32")))), parserParameter); Func<Parser, List<int>> func = lambda.Compile(); Parser parser = new Parser(); var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < MAX; i++) { List<int> list = func(parser); } sw.Stop(); Console.WriteLine("Compiled lambda call: {0} ms", sw.ElapsedMilliseconds); } }

Estos son los resultados en milisegundos en mi computadora:

Direct call: 647 ms Lambda call: 641 ms Compiled lambda call: 5861 ms

No entiendo por qué la llamada lambda compilada es tan lenta.

Y olvidé decir que mi prueba se ejecuta en modo de lanzamiento con la opción "Optimizar código" habilitada.

Actualización : se modificó la evaluación comparativa basada en DateTime.Now to Stopwatch .

¿Alguien sabe cómo ajustar la expresión lambda para obtener un mejor rendimiento en la llamada lambda compilada?


La prueba no es válida por dos razones:

DateTime.Now no es lo suficientemente preciso para pruebas cortas de micro-benchmarking.

Use la clase Stopwatch lugar. Cuando lo hago, obtengo los siguientes resultados (usando MAX = 100000), en milisegundos:

Lambda call: 86.3196 Direct call: 74.057 Compiled lambda call: 814.2178

De hecho, la "llamada directa" es más rápida que la "llamada lambda", lo cual tiene sentido: la "llamada directa" implica llamadas a un delegado que se refiere directamente a un método en un objeto Parser . La "llamada lambda" requiere una llamada a un delegado que se refiere a un método en un objeto de cierre generado por el compilador, que a su vez llama al método en el objeto Parser . Esta indirección adicional introduce una pequeña aceleración.

La "Llamada lambda compilada" no es lo mismo que la "Llamada lambda"

El "Lambda" se ve así:

() => parser.ReadInt32()

mientras que el "lambda compilado" se ve así:

parser => parser.ReadList(() => parser.ReadInt32())

Hay un paso adicional: crear el delegado incrustado para el lambda interno. En un círculo cerrado, esto es costoso.

EDITAR :

Seguí adelante e inspeccioné el IL de la "lambda" frente a la "lambda compilada" y los descompilé de nuevo a C # "más simple" (ver: Ver el código IL generado a partir de una expresión compilada ).

Para la lambda "no compilada", se ve así:

for (int i = 0; i < 100000; i++) { if (CS$<>9__CachedAnonymousMethodDelegate1 == null) { CS$<>9__CachedAnonymousMethodDelegate1 = new Func<int>(CS$<>8__locals3.<LambdaCall>b__0); } CS$<>8__locals3.parser.ReadList<int>(CS$<>9__CachedAnonymousMethodDelegate1); }

Tenga en cuenta que un único delegado se crea una vez y se almacena en caché.

Mientras que para el "lambda compilado", se ve así:

Func<Parser, List<int>> func = lambda.Compile(); Parser parser = new Parser(); for (int i = 0; i < 100000; i++) { func(parser); }

Donde el objetivo del delegado es:

public static List<int> Foo(Parser parser) { object[] objArray = new object[] { new StrongBox<Parser>(parser) }; return ((StrongBox<Parser>) objArray[0]).Value.ReadList<int> (new Func<int>(dyn_type.<ExpressionCompilerImplementationDetails>{1}lambda_method)); }

Tenga en cuenta que aunque el delegado "externo" se crea solo una vez y se almacena en caché, se crea un nuevo delegado "interno" en cada iteración del ciclo. Sin mencionar otras asignaciones para la matriz de object y la StrongBox<T> .