c# - ¿Por qué hay asignaciones de memoria al llamar a una func
performance delegates (1)
Tengo el siguiente programa que construye un Func local a partir de dos métodos estáticos. Pero extrañamente, cuando perfilo el programa, asignó cerca de un millón de objetos funcionales. ¿Por qué invocar el objeto Func también está creando instancias de Func?
public static class Utils
{
public static bool ComparerFunc(long thisTicks, long thatTicks)
{
return thisTicks < thatTicks;
}
public static int Foo(Guid[] guids, Func<long, long, bool> comparerFunc)
{
bool a = comparerFunc(1, 2);
return 0;
}
}
class Program
{
static void Main(string[] args)
{
Func<Guid[], int> func = x => Utils.Foo(x, Utils.ComparerFunc);
var guids = new Guid[10];
for (int i = 0; i < 1000000; i++)
{
int a = func(guids);
}
}
}
Está utilizando una conversión de grupo de métodos para crear el Func<long, long, bool>
utilizado para el parámetro comparerFunc
. Desafortunadamente, la especificación C # 5 actualmente requiere que se cree una nueva instancia de delegado cada vez que se ejecuta. De la sección 6.6 de la especificación C # 5, que describe la evaluación en tiempo de ejecución de una conversión de grupo de métodos:
Se asigna una nueva instancia del tipo D de delegado. Si no hay suficiente memoria disponible para asignar la nueva instancia, se emite una excepción System.OutOfMemoryException y no se ejecutan más pasos.
La sección para conversiones de funciones anónimas (6.5.1) incluye esto:
Se permiten (pero no se requieren) las conversiones de funciones anónimas semánticamente idénticas con el mismo conjunto (posiblemente vacío) de instancias de variables externas capturadas a los mismos tipos de delegado para devolver la misma instancia de delegado.
... pero no hay nada similar para las conversiones de grupos de métodos.
Eso significa que este código puede optimizarse para usar una instancia de un solo delegado para cada uno de los delegados involucrados, y Roslyn lo hace.
Func<Guid[], int> func = x => Utils.Foo(x, (a, b) => Utils.ComparerFunc(a, b));
Otra opción sería asignar el Func<long, long, bool>
una vez y almacenarlo en una variable local. Esa variable local tendría que ser capturada por la expresión lambda, lo que evita que se Func<Guid[], int>
caché la Func<Guid[], int>
, lo que significa que si ejecutó Main
varias veces, crearía dos nuevos delegados en cada llamada, mientras que La solución anterior se almacenaría en caché en la medida de lo razonable El código es más simple sin embargo:
Func<long, long, bool> comparer = Utils.ComparerFunc;
Func<Guid[], int> func = x => Utils.Foo(x, comparer);
var guids = new Guid[10];
for (int i = 0; i < 1000000; i++)
{
int a = func(guids);
}
Todo esto me entristece, y en la última edición del estándar C # de ECMA, se permitirá al compilador almacenar en caché el resultado de las conversiones de grupos de métodos. No sé cuándo / si lo hará sin embargo.