Кэширование дорогостоящих данных в C++ — статика в области функций и изменяемые переменные-члены
-
03-07-2019 - |
Вопрос
У меня есть относительно дорогая операция получения данных, результаты которой я хочу кэшировать.Эта операция вызывается из const
методы, примерно такие:
double AdjustData(double d, int key) const {
double factor = LongRunningOperationToFetchFactor(key);
return factor * d;
}
Я хотел бы AdjustData
оставаться const
, но я хочу кэшировать фактор, чтобы получить его только в первый раз.В настоящее время я использую mutable map<int, double>
для сохранения результата (карта взята из key
к factor
), но я думаю, что использование статики в области функции может быть лучшим решением - этот фактор необходим только этой функции и не имеет отношения к остальной части класса.
Кажется, это хороший путь?Есть ли лучшие варианты?О чем я мог бы подумать, особенно в отношении потокобезопасности.
Спасибо,
Дом
Решение
Я бы обернул реализацию LongRunningOperationToFetchFactor чем-то вроде этого.Я использую блокировки с областью действия Boost, но вы можете сделать что-то подобное с другими платформами блокировки.
#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;
}
Если это действительно длительная операция, то стоимость блокировки мьютекса должна быть минимальной.
Из вашего вопроса было не совсем понятно, но если функция LongRunningOperationToFetchFactor является функцией-членом вашего класса, то вы хотите, чтобы карта была изменяемой картой в том же классе.Однако один статический мьютекс для доступа по-прежнему достаточно быстр.
Другие советы
Я бы нет сделайте этот кеш локальным статическим.Изменяемая карта — это тот решение для кэширования результатов.В противном случае ваша функция станет бесполезной, поскольку разные объекты вашего класса будут использовать один и тот же кеш, поскольку локальный статический кеш одинаков для всех объектов.Однако вы можете использовать локальную статику, если результат не зависит от объекта.Но тогда я бы спросил себя, почему функция является нестатическим членом вашего объекта, если ей не требуется доступ к какому-либо его состоянию.
Как вы говорите, это должно быть потокобезопасным - если разные потоки могут вызывать функцию-член одного и того же объекта, вы, вероятно, захотите использовать мьютекс. boost::thread
хорошая библиотека для использования.
Вы можете использовать одноэлементный шаблон(1) с классом, который выполняет длительную операцию и кэширует результат.Этот экземпляр затем можно было бы использовать в константных функциях-членах других классов.Рассмотрите возможность взаимного исключения, чтобы защитить вставки и извлечения из структуры данных карты для обеспечения безопасности потоков.Если многопоточная производительность представляет собой огромную проблему, вы можете пометить ключи как выполняющиеся, чтобы предотвратить одновременное вычисление одного и того же ключа несколькими потоками.
#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) Шаблон Singleton можно сильно упустить.Поэтому, пожалуйста, не сходите с ума, если вы видите это впервые.
Если я не понимаю, мне кажется очевидным, что вы хотите сделать это статическим:
double AdjustData(double d) const {
static const double kAdjustFactor = LongRunningOperationToFetchFactor();
return kAdjustFactor * d;
}
Таким образом, вы получаете фактор только один раз.