Caching de dados caros em C ++ - estática no escopo da função vs variáveis ??membro mutáveis
-
03-07-2019 - |
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
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.