8
votes

Résultats de la fonction de cache

Pour le plaisir, je joue avec une classe pour répondre facilement aux résultats de la fonction de cache. L'idée de base est que vous pouvez prendre n'importe quelle fonction souhaitée - bien que vous souhaitiez l'utiliser uniquement pour des fonctions relativement coûteuses - et enveloppez-la facilement pour utiliser des recherches de dictionnaire relativement peu coûteuses pour des courses ultérieures avec le même argument. Il n'y a vraiment pas beaucoup à cela:

public class AutoCache<TKey, TValue> 
{  
    public AutoCache(Func<TKey, TValue> FunctionToCache)
    {
        _StoredFunction = FunctionToCache;
        _CachedData = new Dictionary<TKey, TValue>();
    }

    public TValue GetResult(TKey Key)
    {
        if (!_CachedData.ContainsKey(Key)) 
            _CachedData.Add(Key, _StoredFunction(Key));
        return _CachedData[Key];
    }

    public void InvalidateKey(TKey Key)
    {
        _CachedData.Remove(Key);
    }

    public void InvalidateAll()
    {
        _CachedData.Clear();
    }

    private Dictionary<TKey, TValue> _CachedData;
    private Func<TKey, TValue> _StoredFunction; 
}
  • Cela nécessite une fonction qui renvoie le même résultat pour un ensemble d'arguments donné (il doit être apatride). Probablement aucun moyen de changer cela. Li>
  • Il est limité à une gamme de délégués très étroite. Pourrions-nous l'élargir facilement pour fonctionner facilement pour une fonction qui accepte au moins un paramètre et renvoie une valeur, peut-être en emballant des arguments dans un type anonyme? Ou auriez-nous besoin d'une implémentation supplémentaire pour chaque délégué de fonctionnement que nous voulions soutenir? Si oui, pouvons-nous construire une classe abstraite pour rendre cela plus facile? Li>
  • Ce n'est pas le fil-coffre-fort. Li>
  • Pas d'invalidation automatique. Cela rend le danger pour la collecte des ordures. Vous devez le garder pendant un certain temps pour que cela soit utile, et cela signifie que vous n'allez jamais vraiment jeter les objets de cache anciens et potentiellement non nécessaires. LI>
  • Pouvons-nous hériter de ceci pour rendre le cache bidirectionnel pour le cas où la fonction a un seul argument? LI> ul>

    comme point de référence, si je l'utilise dans le code réel, le lieu le plus probable que je l'envisage est dans le cadre d'une couche logique commerciale, où j'utilise ce code pour envelopper une méthode dans l'accès des données. couche qui tire simplement les données d'une table de recherche. Dans ce cas, le voyage de base de données serait coûteux par rapport au dictionnaire et il serait presque toujours exactement une valeur "clé" pour la recherche, c'est donc une bonne correspondance. P> P>


0 commentaires

4 Réponses :


9
votes

Un autre nom pour cette mise en cache automatique des résultats de la fonction est la mémoisation. Pour une interface publique, considérons quelque chose sur ces lignes: xxx

... et utilisez simplement un polymorphisme pour stocker T dans un dictionnaire d'objet.

Extencation de la gamme de délégués pourrait être mis en œuvre via une application de fonction de curry et de fonction partielle. Quelque chose comme ceci: xxx

car curry tourne les fonctions de plusieurs arguments en fonctions d'arguments uniques (mais qui peuvent renvoyer des fonctions), les valeurs de retour sont Admissible à la mémoire de mémoisation elle-même.

Une autre façon de le faire serait d'utiliser la réflexion pour inspecter le type de délégué et stocker des tuples dans le dictionnaire plutôt que simplement le type d'argument. Un tuple simpliste serait simplement un wrapper de tableau dont la logique de croix et de l'égalité utilisait des comparaisons et des hachages profondes.

Invalidation pourrait être aidée avec des références faibles, mais la création de dictionnaires avec affectement est délicat - Il est préférable de faire avec le soutien du temps d'exécution (les valeurs d'affaiblissement sont beaucoup plus faciles). Je crois qu'il y a des implémentations là-bas.

La sécurité du fil est facilement effectuée en verrouillant sur le dictionnaire interne des événements de mutation, mais avoir un dictionnaire sans verrouillage peut améliorer les performances dans des scénarios fortement simultanés. Ce dictionnaire serait probablement encore plus difficile à créer - il y a une présentation intéressant sur un pour Java cependant.


1 commentaires

Nice, je vais lire plus sur la mémoisation. Dans ce cas, je ne pense pas que le currying aidera vraiment ici. Oui, il permet d'utiliser ce code pour gérer une fonction avec plusieurs arguments, mais il ne le fait pas d'une manière qui n'est pas évidente pour l'utilisateur de la classe, qui défait le point de la classe.



0
votes

Étant donné que cela est pour la valeur éducative principalement - vous devez jeter un coup d'œil à la classe d'affaiblissement, ce qui permet au GC de dégager des poignées inutilisées de votre classe dans un environnement multi-fileté. C'est un motif de mise en cache assez courant dans .NET

QUI DIT - CAVEZIER Emputer! Chaque cache est différente. En construisant une solution de capture, vous vous retrouvez souvent dans un cas pathologique où votre "cache" est juste un dictionnaire glorifié avec de nombreuses méthodes d'assistance compliquées qui rendent votre code Hard Toread.


0 commentaires

3
votes

WOW - Quelle seroie - j'avais récemment posté une question sur clés opaques en C # ... et parce que j'essaie de mettre en œuvre quelque chose lié à la mise en cache des résultats de la fonction. Comme c'est drôle.

Ce type de métaprogrammation peut être délicat avec C # ... surtout parce que les paramètres de type générique peuvent entraîner une duplication de code maladroite. Vous finissez souvent à répéter presque le même code dans plusieurs endroits, avec différents paramètres de type, pour atteindre la sécurité de type. P>

Voici ma variation de votre approche qui utilise mon modèle de clé et des fermetures opaques pour créer des fonctions cachables. L'échantillon ci-dessous démontre le motif avec un ou deux arguments, mais il est relativement facile de s'étendre à plus. Il utilise également des méthodes d'extension pour créer un motif transparent pour envelopper une fonction de fonctionnement avec une fonction CACHABLE à l'aide de la méthode ascommable () code>. Les fermetures capturent le cache associé à la fonction - et rendent l'existence transparent envers les autres appelants. P>

Cette technique présente de nombreuses mêmes limitations que votre approche (sécurité du fil, tenant des références, etc.) - Je soupçonne qu'ils ne sont pas trop difficiles à surmonter - mais cela supporte un moyen facile de s'étendre à plusieurs paramètres et permet aux fonctions cachables d'être complètement substituables avec des personnes ordinaires - car ils ne sont qu'un délégué wrapper. P> Il convient également de noter que si vous créez une deuxième instance du fonctionnement cacheable - vous obtenez un cache séparé. Cela peut être à la fois une force et une faiblesse ... car certaines situations que vous ne pouvez pas vous rendre compte que cela se produit. P>

Voici le code: p>

public interface IFunctionCache
{
    void InvalidateAll();
    // we could add more overloads here...
}

public static class Function
{
    public class OpaqueKey<A, B>
    {
        private readonly object m_Key;

        public A First { get; private set; }
        public B Second { get; private set; }

        public OpaqueKey(A k1, B k2)
        {
            m_Key = new { K1 = k1, K2 = k2 };
            First = k1;
            Second = k2;
        }

        public override bool Equals(object obj)
        {
            var otherKey = obj as OpaqueKey<A, B>;
            return otherKey == null ? false : m_Key.Equals(otherKey.m_Key);
        }

        public override int GetHashCode()
        {
            return m_Key.GetHashCode();
        }
    }

    private class AutoCache<TArgs,TR> : IFunctionCache
    {
        private readonly Dictionary<TArgs,TR> m_CachedResults 
            = new Dictionary<TArgs, TR>();

        public bool IsCached( TArgs arg1 )
        {
            return m_CachedResults.ContainsKey( arg1 );
        }

        public TR AddCachedValue( TArgs arg1, TR value )
        {
            m_CachedResults.Add( arg1, value );
            return value;
        }

        public TR GetCachedValue( TArgs arg1 )
        {
            return m_CachedResults[arg1];
        }

        public void InvalidateAll()
        {
            m_CachedResults.Clear();
        }
    }

    public static Func<A,TR> AsCacheable<A,TR>( this Func<A,TR> function )
    {
        IFunctionCache ignored;
        return AsCacheable( function, out ignored );
    }

    public static Func<A, TR> AsCacheable<A, TR>( this Func<A, TR> function, out IFunctionCache cache)
    {
        var autocache = new AutoCache<A,TR>();
        cache = autocache;
        return (a => autocache.IsCached(a) ?
                     autocache.GetCachedValue(a) :
                     autocache.AddCachedValue(a, function(a)));
    }

    public static Func<A,B,TR> AsCacheable<A,B,TR>( this Func<A,B,TR> function )
    {
        IFunctionCache ignored;
        return AsCacheable(function, out ignored);
    }

    public static Func<A,B,TR> AsCacheable<A,B,TR>( this Func<A,B,TR> function, out IFunctionCache cache )
    {
        var autocache = new AutoCache<OpaqueKey<A, B>, TR>();
        cache = autocache;
        return ( a, b ) =>
                   {
                       var key = new OpaqueKey<A, B>( a, b );
                       return autocache.IsCached(key)
                                  ? autocache.GetCachedValue(key)
                                  : autocache.AddCachedValue(key, function(a, b));
                   };
    }
}

public class CacheableFunctionTests
{
    public static void Main( string[] args )
    {
        Func<string, string> Reversal = s => new string( s.Reverse().ToArray() );

        var CacheableReverse = Reversal.AsCacheable();

        var reverse1 = CacheableReverse("Hello");
        var reverse2 = CacheableReverse("Hello"); // step through to prove it uses caching

        Func<int, int, double> Average = (a,b) => (a + b)/2.0;
        var CacheableAverage = Average.AsCacheable();

        var average1 = CacheableAverage(2, 4);
        var average2 = CacheableAverage(2, 4);
    }
}


0 commentaires

0
votes

J'utilise cette extension simple, qui utilise dans ce cas MemoryCache: xxx pré>

exemple / Utilisation: (la durée de cache est de 42 secondes): P>

    public class CachedCalculator
    {
        private Func<int, int> _heavyExpensiveMultiplier;

        public Calculator(Func<int,int> heavyExpensiveMultiplier )
        {
            //wrap function into cached one
            this._heavyExpensiveMultiplier 
              = heavyExpensiveMultiplier.Cached(x =>/*key for cache*/ x.ToString(), TimeSpan.FromSeconds(42));
        }

        //this uses cached algorithm
        public int Compute(int x)
        {
            return _heavyExpensiveMultiplier(x);
        }
    }


0 commentaires