Кэширование дорогостоящих данных в C++ — статика в области функций и изменяемые переменные-члены

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

Вопрос

У меня есть относительно дорогая операция получения данных, результаты которой я хочу кэшировать.Эта операция вызывается из 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;
}

Таким образом, вы получаете фактор только один раз.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top