Mise en cache de données coûteuses en C ++ - statique portée par une fonction vs variables membres mutables

StackOverflow https://stackoverflow.com/questions/614875

Question

Je souhaite mettre en cache les résultats d'une opération d'extraction de données relativement coûteuse. Cette opération est appelée à partir de const méthodes, à peu près comme ceci:

double AdjustData(double d, int key) const {
  double factor = LongRunningOperationToFetchFactor(key);
  return factor * d;
}

Je voudrais que AdjustData reste mutable map<int, double>, mais je veux cacher le facteur afin que je ne le récupère que la première fois. À l’heure actuelle, j’utilise un key pour stocker le résultat (la carte va de factor à <=>), mais je pense qu’utiliser une source statique avec une fonction étendue pourrait être une meilleure solution - ce facteur n’est nécessaire que par cette fonction, et est sans rapport avec le reste de la classe.

Cela vous semble-t-il être une bonne solution? Y a-t-il de meilleures options? À quoi pourrais-je penser, en particulier en ce qui concerne la sécurité des threads.

Merci,

Dom

Était-ce utile?

La solution

Je voudrais envelopper l'implémentation de LongRunningOperationToFetchFactor avec quelque chose comme ça. J'utilise des verrous Boost Scoped, mais vous pouvez utiliser quelque chose de similaire avec d'autres cadres de verrouillage.

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <map>

using namespace std;

static boost::mutex myMutex;
static map<int,double> results;

double CachedLongRunningOperationToFetchFactor( int key )
{

   {
       boost::mutex::scoped_lock lock(myMutex);

       map<int,double>::iterator iter = results.find(key);
       if ( iter != results.end() )
       {
          return (*iter).second;
       }
   }
   // not in the Cache calculate it
   result = LongRunningOperationToFetchFactor( key );
   {
       // we need to lock the map again
       boost::mutex::scoped_lock lock(myMutex);
       // it could be that another thread already calculated the result but
       // map assignment does not care.
       results[key] = result;
   }
   return result;
}

S'il s'agit vraiment d'une opération de longue durée, le coût du verrouillage du Mutex devrait être minime.

La question que vous posez n'est pas claire, mais si la fonction LongRunningOperationToFetchFactor est une fonction membre de votre classe, vous souhaitez que la carte soit mutable dans cette même classe. Je simple mutex statique pour l'accès est encore assez rapide cependant.

Autres conseils

Je ne voudrais pas faire de ce cache une statique locale. La carte mutable est la solution de mise en cache des résultats. Sinon, votre fonction deviendra inutile, car différents objets de votre classe partageront le même cache, car le cache statique local est le même pour tous les objets. Vous pouvez utiliser la statique locale si le résultat ne dépend pas de l'objet. Mais ensuite, je me demanderais pourquoi la fonction est un membre non statique de votre objet, si elle n'a pas besoin d'accéder à aucun état de celle-ci.

Comme vous le dites, il devrait être thread-safe - si différents threads peuvent appeler la fonction membre sur le même objet, vous souhaiterez probablement utiliser un mutex. boost::thread est une bonne bibliothèque à utiliser.

Vous pouvez utiliser le modèle singleton (1) avec une classe effectuant le long -running opération et met en cache le résultat. Cette instance pourrait ensuite être utilisée dans les fonctions membres const d’autres classes. Envisagez une exclusion mutuelle pour protéger les insertions et les extractions de la structure de données de la carte afin de garantir la sécurité des threads. Si les performances multithreads sont un problème énorme, vous pouvez alors marquer les clés en cours pour empêcher plusieurs threads de calculer la même clé simultanément.

#include <cstdlib>
#include <iostream>
#include <map>

using namespace std;

class FactorMaker {
    map<int, double> cache;

    double longRunningFetch(int key)
    {
        const double factor = static_cast<double> (rand()) / RAND_MAX;
        cout << "calculating factor for key " << key << endl;
        // lock
        cache.insert(make_pair(key, factor));
        // unlock
        return factor;
    }

public:
    double getFactor(int key) {
        // lock
        map<int, double>::iterator it = cache.find(key);
        // unlock
        return (cache.end() == it) ? longRunningFetch(key) : it->second;
    }
};

FactorMaker & getFactorMaker()
{
    static FactorMaker instance;
    return instance;
}

class UsesFactors {
public:
    UsesFactors() {}

    void printFactor(int key) const
    {
        cout << getFactorMaker().getFactor(key) << endl;
    }
};

int main(int argc, char *argv[])
{
    const UsesFactors obj;

    for (int i = 0; i < 10; ++i)
        obj.printFactor(i);

    for (int i = 0; i < 10; ++i)
        obj.printFactor(i);

    return EXIT_SUCCESS;
}

(1) Le motif singleton peut être grossièrement omis. Alors, évitez de vous laisser aller si vous le voyez pour la première fois.

À moins que je ne comprenne pas, il me semble évident que vous voulez en faire une photo statique:

double AdjustData(double d) const {
   static const double kAdjustFactor = LongRunningOperationToFetchFactor();
   return kAdjustFactor * d;
}

Ainsi, vous ne récupérez le facteur qu'une seule fois.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top