Memorizzazione nella cache di dati costosi in C ++ - statica con ambito funzionale vs variabili membro mutabili

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

Domanda

Ho un'operazione di recupero dei dati relativamente costosa di cui voglio memorizzare nella cache i risultati. Questa operazione è chiamata da const metodi, più o meno in questo modo:

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

Vorrei AdjustData rimanere mutable map<int, double>, ma voglio mettere in cache il fattore in modo da recuperarlo solo la prima volta. Al momento sto usando un key per memorizzare il risultato (la mappa è da factor a <=>), ma sto pensando che usare una statica con ambito funzionale potrebbe essere una soluzione migliore - questo fattore è necessario solo da questa funzione ed è irrilevante per il resto della classe.

Sembra una buona strada da percorrere? Ci sono opzioni migliori? A cosa potrei pensare, in particolare per quanto riguarda la sicurezza dei thread.

Grazie,

Dom

È stato utile?

Soluzione

Vorrei concludere l'implementazione di LongRunningOperationToFetchFactor con qualcosa del genere. Sto usando i blocchi con ambito Boost ma puoi fare qualcosa di simile con altri framework di blocco.

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

Se questa è davvero un'operazione di lunga durata, il costo del blocco del Mutex dovrebbe essere minimo.

Non è stato del tutto chiaro dalla tua domanda, ma se la funzione LongRunningOperationToFetchFactor è una funzione membro della tua classe, allora vuoi che la mappa sia mutabile in quella stessa classe. Il singolo mutex statico per l'accesso è comunque abbastanza veloce.

Altri suggerimenti

Vorrei non rendere questa cache statica locale. La mappa mutabile è la soluzione per i risultati della memorizzazione nella cache. Altrimenti renderà la tua funzione inutile, poiché diversi oggetti della tua classe condivideranno la stessa cache, poiché la cache statica locale è la stessa per tutti gli oggetti. È possibile utilizzare la statica locale se il risultato non dipende dall'oggetto. Ma poi mi chiedo perché la funzione è un membro non statico del tuo oggetto, se non è necessario accedervi.

Come dici tu dovrebbe essere thread-safe - se thread diversi possono chiamare la funzione membro sullo stesso oggetto, probabilmente vorrai usare un mutex. boost::thread è una buona libreria da usare.

Puoi usare il modello singleton (1) con una classe che esegue il long -operazione in esecuzione e memorizza nella cache il risultato. Questa istanza potrebbe quindi essere utilizzata nelle funzioni membro const di altre classi. Prendere in considerazione l'esclusione reciproca per proteggere inserti ed estrazioni dalla struttura dei dati della mappa per la sicurezza del thread. Se le prestazioni multi-thread sono un grosso problema, puoi contrassegnare le chiavi come in corso per impedire a più thread di calcolare la stessa chiave contemporaneamente.

#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) Il modello singleton può essere gravemente mancato. Quindi, per favore, astieniti dal impazzire se lo vedi per la prima volta.

A meno che non capisca, mi sembra ovvio che tu voglia renderlo statico:

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

In questo modo puoi recuperare il fattore solo una volta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top