Кэширование дорогих данных в C ++ - статика в функциональной области и изменяемые переменные-члены - PullRequest
9 голосов
/ 05 марта 2009

У меня относительно дорогая операция извлечения данных, для которой я хочу кэшировать результаты. Эта операция вызывается из const методов, примерно так:

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

Я бы хотел, чтобы AdjustData остался const, но я хочу кешировать фактор, поэтому я получаю его только в первый раз. В настоящее время я использую mutable map<int, double> для сохранения результата (карта имеет значение от key до factor), но я думаю, что использование статической области действия может быть лучшим решением - этот фактор необходим только этой функцией и не имеет отношения к остальной части класса.

Похоже, это хороший путь? Есть ли лучшие варианты? О чем я могу подумать, особенно в отношении безопасности потоков.

Спасибо

Дом

Ответы [ 4 ]

5 голосов
/ 05 марта 2009

Я бы обернул реализацию 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;
}

Если это действительно длительная операция, то стоимость блокировки Mutex должна быть минимальной.

Это было не совсем понятно из вашего вопроса, но если функция LongRunningOperationToFetchFactor является функцией-членом вашего класса, то вы хотите, чтобы карта была изменяемой картой в этом же классе. Единственный статический мьютекс для доступа все еще достаточно быстр.

3 голосов
/ 05 марта 2009

Я бы не сделал бы этот кэш локальным статическим. Изменяемая карта - это решение для кэширования результатов. В противном случае это сделает вашу функцию бесполезной, так как разные объекты вашего класса будут использовать один и тот же кеш, так как локальный статический кеш одинаков для всех объектов. Вы можете использовать локальную статику, если результат не зависит от объекта. Но тогда я хотел бы спросить себя, почему функция является нестатическим членом вашего объекта, если ей не требуется доступ к какому-либо состоянию этого объекта.

Как вы говорите, это должно быть поточно-ориентированным - если разные потоки могут вызывать функцию-член для одного и того же объекта, вы, вероятно, захотите использовать мьютекс. boost::thread - хорошая библиотека для использования.

1 голос
/ 05 марта 2009

Вы можете использовать одноэлементный шаблон (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) Одиночный паттерн может быть пропущен. Поэтому, пожалуйста, воздержитесь от того, чтобы сходить с ума от этого, если вы видите это впервые.

0 голосов
/ 05 марта 2009

Если я не понимаю, мне кажется очевидным, что вы хотите сделать это статическим:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...