web-dev-qa-db-fra.com

Pourquoi y a-t-il des allocations de mémoire lors de l'appel d'un func

J'ai le programme suivant qui construit un Func local à partir de deux méthodes statiques. Mais étrangement, lorsque j'ai profilé le programme, il a alloué près d'un million d'objets Func. Pourquoi invoquer un objet Func crée également des instances Func?

enter image description here

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);
        }
    }
}
47
Xiaoguo Ge

Vous utilisez un conversion de groupe de méthodes pour créer le Func<long, long, bool> utilisé pour le paramètre comparerFunc. Malheureusement, la spécification C # 5 actuellement nécessite celle de créer une nouvelle instance de délégué à chaque exécution. À partir de la section 6.6 de la spécification C # 5, décrivant l'évaluation au moment de l'exécution d'une conversion de groupe de méthodes:

Une nouvelle instance du type délégué D est allouée. S'il n'y a pas assez de mémoire disponible pour allouer la nouvelle instance, une System.OutOfMemoryException est levée et aucune autre étape n'est exécutée.

La section pour les conversions de fonctions anonymes (6.5.1) comprend ceci:

Les conversions de fonctions anonymes sémantiquement identiques avec le même ensemble (éventuellement vide) d'instances de variables externes capturées vers les mêmes types de délégués sont autorisées (mais pas obligatoires) pour renvoyer la même instance de délégué.

... mais il n'y a rien de semblable pour les conversions de groupes de méthodes.

Cela signifie que ce code est autorisé à optimiser pour utiliser une seule instance de délégué pour chacun des délégués impliqués - et Roslyn le fait.

Func<Guid[], int> func = x => Utils.Foo(x, (a, b) => Utils.ComparerFunc(a, b));

Une autre option serait d'allouer le Func<long, long, bool> une fois et le stocker dans une variable locale. Cette variable locale devrait être capturée par l'expression lambda, ce qui empêche le Func<Guid[], int> de la mise en cache - ce qui signifie que si vous exécutiez Main plusieurs fois, vous créeriez deux nouveaux délégués à chaque appel, tandis que la solution précédente mettait en cache autant que possible. Le code est cependant plus simple:

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);
}

Tout cela me rend triste, et dans la dernière édition de la norme ECMA C #, le compilateur sera autorisé pour mettre en cache le résultat des conversions de groupes de méthodes. Je ne sais pas quand/si cela le fera cependant.

52
Jon Skeet