Caching de dados caros em C ++ - estática no escopo da função vs variáveis ??membro mutáveis

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

Pergunta

Eu tenho uma operação de busca de dados relativamente caro que eu quero para armazenar em cache os resultados. Esta operação é chamada de métodos const, mais ou menos assim:

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

Eu gostaria AdjustData permanecer const, mas eu quero para armazenar em cache o fator então eu só buscá-la pela primeira vez. No momento eu estou usando um mutable map<int, double> para armazenar o resultado (o mapa ser de key para factor), mas estou pensando usando um estático escopo de função pode ser uma solução melhor - este fator só é necessária por esta função, e é irrelevante para o resto da classe.

O que parece ser um bom caminho a percorrer? Existem as opções melhores? Que coisas que eu poderia pensar, nomeadamente em matéria de thread-segurança.

Obrigado,

Dom

Foi útil?

Solução

Eu envolveria a implementação de LongRunningOperationToFetchFactor com algo parecido com isso. Estou usando o impulso escopo fechaduras, mas você pode de modo algo semelhante com outras estruturas de bloqueio.

#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 isso realmente é uma operação de longa duração, em seguida, o custo de bloquear o Mutex deve ser mínima.

Não foi muito claro de que você pergunta, mas se a função LongRunningOperationToFetchFactor é uma função membro de você classe, então você quer o mapa do mapa mutável estar nessa mesma classe. I mutex estática única para o acesso ainda é rápido o suficiente embora.

Outras dicas

Eu faria não fazer esta cache uma estática local. O mapa mutável é a solução para o cache de resultados. Caso contrário, ele vai fazer a sua função inútil, como diferentes objetos de sua classe irão compartilhar o mesmo cache, como o cache estática local é o mesmo para todos os objetos. Você pode usar o local estático se o resultado não depende do objeto embora. Mas então eu me pergunto por que a função é membro não-estático do seu objeto, se não precisar acessar qualquer estado do mesmo.

Como você dizer que deve ser thread-safe - se diferentes tópicos pode chamar a função de membro no mesmo objeto, você provavelmente vai querer usar um mutex. boost::thread é uma biblioteca boa de usar.

Você pode usar o singleton padrão (1) com uma classe que executa as longas -Running operação e armazena o resultado. Este exemplo pode em seguida ser utilizada em funções membro const de outras classes. Considere a exclusão mútua de proteger inserções e extrações da estrutura de dados de mapa para a segurança do thread. Se o desempenho de multi-threaded é um enorme problema, então você pode chaves marcar como em andamento para evitar vários segmentos de cálculo da mesma chave simultaneamente.

#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) O padrão Singleton pode ser grosseiramente perdido. Então, por favor, abster-se de ficar louco com isso, se você está vendo pela primeira vez.

A não ser que eu não entendo, parece-me óbvio que você quer fazer isto uma estática:

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

Dessa forma, você só buscar o fator de uma vez.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top