2009-03-05 11 views
7

У меня относительно дорогостоящая операция извлечения данных, которую я хочу кэшировать. Эта операция вызывается из const методов, примерно так:Кэширование дорогостоящих данных на C++ - статическая статистика с функциями с изменяемыми переменными-членами

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

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

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

Спасибо,

Dom

ответ

5

Я бы обернул реализацию 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 является функцией-членом вашего класса, то вы хотите, чтобы карта была изменчивой в том же классе. Я один статический мьютекс для доступа все еще достаточно быстро, хотя.

+0

Возможно, нет необходимости блокировать длительную работу, просто найдите/вставьте вызовы на карту. –

+0

Я думаю, что ты прав. Позвольте мне настроить его дальше. –

+0

Создание статической карты при сохранении блокировки не гарантируется потокобезопасностью, поскольку вы можете дважды построить и дважды разрушить карту, если два потока одновременно вызовут эту функцию при первом использовании. См. Http://blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspx – bk1e

0

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

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

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

+0

Hi Lyndsey - спасибо, это было то, о чем я думал. Я немного отредактировал свой вопрос, чтобы отразить реальную проблему. Я не могу использовать статический const в этом случае, поэтому, я думаю, я использую статическую карту и делаю какую-то блокировку вокруг вставок карты. Правильно ли это? –

+0

То, о чем вы говорите, будет работать, но решение имеет несколько * плохой запах :) (взято из книги Рефакторинг). Это что-то, что пользователь инициирует для множества разных «ключей», которые вы можете запускать в отдельном потоке и отображать результат, когда это делается? –

+0

Если вы сделаете его статическим, все объекты класса разделит значение. Это то, что вы хотите? –

1

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

3

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

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

+1

Таким образом, функция является нестатической частью объекта, поскольку она ранее зависела от состояния объекта (изменчивая карта). С помощью локального статического я могу сделать эту функцию статической. –

+0

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