51 votes

Mise en cache des données de réflexion

Quelle est la meilleure façon de cache coûteux données obtenues à partir de la réflexion? Par exemple, la plupart des fast-sérialiseurs cache ces informations de sorte qu'ils n'ont pas besoin de réfléchir à chaque fois qu'ils rencontrent le même type de nouveau. Ils peuvent même générer une dynamique de la méthode dont ils chercher de la type.

Avant de .net 4

Traditionnellement, j'ai utilisé un normal dictionnaire statique pour que. Par exemple:

private static ConcurrentDictionary<Type, Action<object>> cache;

public static DoSomething(object o)
{
    Action<object> action;
    if(cache.TryGetValue(o.GetType(), out action)) //Simple lookup, fast!
    {
        action(o);
    }
    else
    {
        // Do reflection to get the action
        // slow
    }
} 

Ceci apporte un peu de mémoire, mais comme il ne fait qu'une seule fois par Type et types vécu aussi longtemps que l' AppDomain je ne pense pas que d'un problème.

Depuis .net 4

Mais maintenant .net 4 introduit à Collectionner les Assemblages de Type Dynamique de Génération. Si j'ai jamais utilisé, DoSomething sur un objet déclaré dans les collectionner assemblée que l'assemblée ne sera jamais déchargé. Ouch.

Alors, quelle est la meilleure façon de cache par des informations de type dans .net 4 qui ne souffre pas de ce problème? La solution la plus simple je pense est de un:

private static ConcurrentDictionary<WeakReference, TCachedData> cache.

Mais l' IEqualityComparer<T> j'aurais à utiliser avec celle-ci se comporte très étrangement, et que cela risquait de violer le contrat. Je ne suis pas sûr de la façon rapide de la recherche serait soit.

Une autre idée est d'utiliser une expiration de délai d'attente. Peut-être la solution la plus simple, mais se sent un peu inélégant.


Dans le cas où le type est fourni en paramètre générique je peux utiliser un imbriquée classe générique qui ne devrait pas souffrir de ce problème. Mais sa ne fonctionne pas si le type est fourni dans une variable.

class MyReflection
{
    internal Cache<T>
    {
        internal static TData data;
    }

    void DoSomething<T>()
    {
        DoSomethingWithData(Cache<T>.data);
        //Obviously simplified, should have similar creation logic to the previous code.
    }
}

Mise à jour: Une idée que je viens a l'aide d' Type.AssemblyQualifiedName comme clé. Que doit identifier de manière unique ce type sans le garder en mémoire. Je pourrais même sortir avec l'aide d'identité référentielle sur cette chaîne.

Un problème qui reste avec cette solution est que la valeur mise en cache peut conserver une référence au type de trop. Et si j'utilise une référence faible pour qu'il sera très probablement expirer loin devant l'assemblée d'être déchargé. Et je ne suis pas sûr de comment pas cher il est normal de référence d'une référence faible. Semble que j'ai besoin de faire quelques tests et d'analyse comparative.

37voto

Ivan Danilov Points 5719

ConcurrentDictionary<WeakReference, CachedData> est incorrecte dans ce cas. Supposons que nous essayons de cache info pour le type T, alors WeakReference.Target==typeof(T). CachedData très probablement contenir de référence pour l' typeof(T) également. En tant que ConcurrentDictionary<TKey, TValue> stocke les éléments dans l'intérieur de la collection de l' Node<TKey, TValue> vous aurez la chaîne de solides références: ConcurrentDictionary instance -> Node instance -> Value de la propriété (CachedData exemple) -> typeof(T). En général, il est impossible d'éviter la fuite de mémoire avec WeakReference dans le cas lorsque les Valeurs pourraient avoir des références à leurs Clés.

Il est nécessaire d'ajouter le support pour ephemerons de faire un tel scénario possible sans fuites de mémoire. Heureusement .NET 4.0 prend en charge et nous avons ConditionalWeakTable<TKey, TValue> classe. Il semble que les raisons de l'introduire sont à proximité de votre tâche.

Cette approche résout également le problème mentionné dans votre mise à jour en tant que référence pour Type de vivre exactement aussi longtemps que l'Assemblée est chargé.

2voto

Devin Garner Points 608

Vous devriez vérifier le fasterflect libary sur codeplex http://fasterflect.codeplex.com/

Vous pouvez utiliser de réflexion normale pour générer dynamiquement le nouveau code & émettent/compiler puis mise en cache de la version compilée. Je pense que le collectionner assemblée idée est prometteuse, pour éviter la fuite de mémoire sans avoir à charger/décharger à partir d'un autre domaine d'application. Cependant, la fuite de mémoire devrait être négligeable, sauf si vous êtes à la compilation des centaines de méthodes.

Voici un blog sur dynamiquement la compilation d'un code à l'exécution: http://introspectingcode.blogspot.com/2011/06/dynamically-compile-code-at-runtime.html

Voici une autre concurrente dictionnaire approche que j'ai utilisé dans le passé pour stocker les MethodInfo/PropertyInfo objets et il a l'air d'être plus rapide, mais je pense que c'était dans une vieille version de Silverlight. Je crois .Net a sa propre réflexion interne cache qui le rend inutile.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.Concurrent;

namespace NetSteps.Common.Reflection
{
    public static class Reflection
    {
        private static ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> reflectionPropertyCache = new ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>>();
        public static List<PropertyInfo> FindClassProperties(Type objectType)
        {
            if (reflectionPropertyCache.ContainsKey(objectType))
                return reflectionPropertyCache[objectType].Values.ToList();

            var result = objectType.GetProperties().ToDictionary(p => p.Name, p => p);

            reflectionPropertyCache.TryAdd(objectType, result);

            return result.Values.ToList();
        }

    }
}

-1voto

Wardy Points 2289

J'ai peut-être une lapalissade ici mais:

Ne cache habituellement, les fournisseurs de serialise de données à une source?

Alors certes, la deserialisation processus va être plus cher que le simple le simple reflet d'une nouvelle instance?

Ou ai-je raté quelque chose?

Et il y a toute l'argumentation autour de boxing et unboxing des coûts de temps ... pas sûr que ce soit vraiment compte si.

Edit:

Comment à ce sujet (j'espère que c'est ce qui explique le problème un peu mieux)...

Dictionary<string, Type> typecache = new Dictionary<string, Type>();

// finding a type from say a string that points at a type in an assembly not referrenced
// very costly but we can cache that
Type myType = GetSomeTypeWithReflection();
typecache.Add("myType", myType);

// creating an instance you can use very costly
MyThingy thingy = Activator.CreateInstance(typecache["myType"]);

vous êtes à la recherche d'cache "truc"?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X