C ++での高価なデータのキャッシュ-関数スコープの静的変数と可変メンバー変数
-
03-07-2019 - |
質問
結果をキャッシュしたい比較的高価なデータ取得操作があります。この操作は、おおよそ次のようにconst
メソッドから呼び出されます。
double AdjustData(double d, int key) const {
double factor = LongRunningOperationToFetchFactor(key);
return factor * d;
}
AdjustData
をmutable map<int, double>
のままにしておきたいのですが、ファクターをキャッシュして、最初にフェッチするだけにします。現在、結果を保存するためにkey
を使用しています(マップはfactor
から<=>まで)この関数により、クラスの他の部分とは無関係です。
それは良い方法のように思えますか?より良いオプションはありますか?特にスレッドセーフに関しては、どのようなことを考えますか。
ありがとう、
Dom
解決
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がクラスのメンバー関数である場合、マップを同じクラスの可変マップにする必要があります。ただし、アクセス用の単一の静的ミューテックスはまだ十分に高速です。
他のヒント
このキャッシュをローカルスタティックにするしない。可変マップは、結果をキャッシュするための ソリューションです。そうしないと、クラスの異なるオブジェクトが同じキャッシュを共有するため、ローカルの静的キャッシュがすべてのオブジェクトで同じになるため、関数が役に立たなくなります。ただし、結果がオブジェクトに依存しない場合は、ローカルスタティックを使用できます。しかし、関数の状態にアクセスする必要がない場合、なぜ関数がオブジェクトの非静的メンバーであるのかを自問します。
あなたが言うように、それはスレッドセーフであるべきです-異なるスレッドが同じオブジェクトのメンバー関数を呼び出すことができるなら、おそらくミューテックスを使いたいでしょう。 boost::thread
は使用に適したライブラリです。
シングルトンパターン(1)を、長時間実行するクラスで使用できます。 -操作を実行し、結果をキャッシュします。このインスタンスは、他のクラスのconstメンバー関数で使用できます。スレッドセーフのために、マップデータ構造からの挿入と抽出を保護するために相互排除を検討してください。マルチスレッドのパフォーマンスが大きな問題である場合、複数のスレッドが同じキーを同時に計算しないようにキーを進行中としてフラグを立てることができます。
#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)シングルトンパターンは大きく見落とされる可能性があります。そのため、初めて見た場合は気をつけないでください。
理解していない限り、これを静的にしたいのは明らかです:
double AdjustData(double d) const {
static const double kAdjustFactor = LongRunningOperationToFetchFactor();
return kAdjustFactor * d;
}
この方法では、ファクターを一度だけ取得します。