f(a,b)
2 つの入力を受け入れる関数 があります。a
とのどの値が使用されるかは前もってわかりませんb
。私はメモリを少し無駄にしても大丈夫です (私は速度を気にします)。f(a,b)
の出力が既に配信されているかどうかを確認できるようにしたいのですが、そうであれば、f(a,b)
プロセスを再実行せずにその出力を再度配信します。
デコレータを使って Python で行うのは簡単ですが、C++ は私の頭をはるかに超えています。
f(a,b)
2 つの入力を受け入れる関数 があります。a
とのどの値が使用されるかは前もってわかりませんb
。私はメモリを少し無駄にしても大丈夫です (私は速度を気にします)。f(a,b)
の出力が既に配信されているかどうかを確認できるようにしたいのですが、そうであれば、f(a,b)
プロセスを再実行せずにその出力を再度配信します。
デコレータを使って Python で行うのは簡単ですが、C++ は私の頭をはるかに超えています。
ポスターは次のように尋ねます。
f(a,b) の出力が既に配信されているかどうかを確認できるようにしたいのですが、そうであれば、f(a,b) プロセスを再実行せずにその出力を再度配信します。
を使用してC++で非常に簡単std::map
です。関数が正確に 2 つのパラメーターを持つという事実は、std::pair
それらを記述するために使用できることを意味します。
#include <map>
#include <iostream>
uint64_t real_f(int a, int b) {
std::cout << "*";
// Do something tough:
return (uint64_t)a*b;
}
uint64_t memo_f(int a, int b) {
typedef std::pair<int, int> key;
typedef std::map<key, uint64_t> map;
static map m;
key k(a,b);
map::iterator it = m.find(k);
if(it == m.end()) {
return m[k] = real_f(a, b);
}
return it->second;
}
int main () {
std::cout << memo_f(1, 2) << "\n";
std::cout << memo_f(3, 4) << "\n";
std::cout << memo_f(1, 2) << "\n";
std::cout << memo_f(3, 4) << "\n";
std::cout << memo_f(5, 6) << "\n";
}
上記のプログラムの出力は次のとおりです。
*2
*12
2
12
*30
アスタリスクのない行は、キャッシュされた結果を表します。
C++11 では、タスクとフューチャーを使用できます。あなたの機能をしましょうf
:
int f(int a, int b)
{
// Do hard work.
}
次に、戻り値へのハンドルを返す関数の実行をスケジュールします。このハンドルはfutureと呼ばれます:
template <typename F>
std::future<typename std::result_of<F()>::type>
schedule(F f)
{
typedef typename std::result_of<F()>::type result_type;
std::packaged_task<result_type> task(f);
auto future = task.get_future();
tasks_.push_back(std::move(task)); // Queue the task, execute later.
return std::move(future);
}
次に、このメカニズムを次のように使用できます。
auto future = schedule(std::bind(&f, 42, 43)); // Via std::bind.
auto future = schedule([&] { f(42, 43); }); // Lambda alternative.
if (future.has_value())
{
auto x = future.get(); // Blocks if the result of f(a,b) is not yet availble.
g(x);
}
免責事項: 私のコンパイラはタスク/フューチャーをサポートしていないため、コードに多少の荒いエッジがある場合があります。
この質問の要点は、f(a,b) の計算と、結果をキャッシュするためのある種のルックアップ テーブルの保持との間の CPU と RAM の相対的な費用です。
128 ビットのインデックス長の完全なテーブルは (まだ) 実行可能ではないため、ルックアップ スペースを管理可能なサイズに縮小する必要があります。これは、アプリ内でいくつかの考慮事項なしでは実行できません。
(a,b, f(a,b))
タプルの固定サイズの配列と線形検索から始めるだけです。上記で尋ねたパターンによっては、
(a,b,f(a,b),count)
除外されるタプルを持つ - これは、ローカライズされていないオカレンスに適していますルックアップ メカニズムがますます複雑になる場合は、繰り返し計算をルックアップ メカニズムに対してベンチマークすることもできstd::map
ます。
唯一の簡単な方法は、 を使用することstd::map
です。std::unordered_map
動作しません。std::pair
順序付けられていないマップのキーとして使用することはできません。次のことができます。
std::map<pair<int, int>, int> mp;
int func(int a, int b)
{
if (mp.find({a, b}) != mp.end()) return mp[{a, b}];
// compute f(a, b)...
mp[{a, b}] = // computed value;
return mp[{a, b}];
}