2

多くの小さな関数を含むc++コードがあるとします。各関数には、通常、中間計算の結果を含めるために実行時に既知のn、pの行列float M1(n、p)が必要です(M1を初期化する必要はありません)。 、各関数がM1のすべての行を上書きするため、宣言するだけです)。

この理由の一部は、各関数が変更できない元のデータマトリックスで機能するため、「他の場所」で多くの操作(並べ替え、意味の変更、球形化)を実行する必要があるためです。

各関数内に一時的なM1(n、p)を作成するか、main()内に一度だけ作成して、各関数がスクラップスペースとして使用できる一種のバケットとして各関数に渡す方がよいでしょうか。

nとpは、nの場合は[10 ^ 2-10 ^ 4]、pの場合は[5-100]と適度に大きいことがよくあります。

(元々はcodereview stackexchangeに投稿されていましたが、ここに移動しました)。

一番、

4

4 に答える 4

2

時期尚早の最適化を使用しないでください。適切に機能するものを作成し、遅いことが示された場合は後で最適化します。

(ちなみに、stackoverflowも正しい場所ではないと思います)。

実際には、大きなマトリックスで動作するアプリケーションを高速化したい場合は、並行性を使用することが解決策になります。また、並行性を使用している場合、1つの大きなグローバルマトリックスがあると、はるかに多くの問題が発生する可能性があります。

基本的には、メモリがある場合でも、一度に複数の計算を実行することはできないことを意味します。

マトリックスの設計は最適である必要があります。このデザインを検討する必要があります。

したがって、私は一般的にあなたのコードで言うでしょう、いいえ、あなたがそれでやりたいことに対して間違っているように聞こえるので、1つの大きなグローバルマトリックスを作成しないでください。

于 2012-03-02T07:47:48.850 に答える
2
  1. ヒープの割り当ては確かに非常に高価です。
  2. 時期尚早の最適化は悪いですが、ライブラリが非常に一般的で行列が巨大な場合、効率的な設計を探すのは時期尚早ではないかもしれません。結局のところ、多くの依存関係を蓄積した後でデザインを変更したくはありません。
  3. この問題に取り組むことができるさまざまなレベルがあります。たとえば、メモリアロケータレベル(スレッドごとのメモリプールなど)でヒープ割り当ての費用を回避できます。
  4. ヒープの割り当てにはコストがかかりますが、1つの巨大なマトリックスを作成するのは、マトリックスに対してかなり高価な操作(通常は線形の複雑さ以下)を実行するためだけです。相対的に言えば、フリーストアにマトリックスを割り当てることは、後で必然的に行う必要があるものと比較してそれほど高価ではない可能性があります。したがって、並べ替えなどの関数の全体的なロジックと比較すると、実際にはかなり安価である可能性があります。

将来の可能性として#3を考慮して、自然にコードを書くことをお勧めします。つまり、一時的なものの作成を加速するために、中間計算のために行列バッファーへの参照を取り入れないでください。一時的なものを作成し、値で返します。正確さと優れた明確なインターフェースが最初に来ます。

ここでの主な目標は、マトリックスの作成ポリシーを(アロケータまたはその他の手段を介して)分離することです。これにより、既存のコードをあまり変更せずに、後付けとして最適化する余地が生まれます。関連する関数の実装の詳細のみを変更するか、さらに良いことに、マトリックスクラスの実装のみを変更することでそれを実行できる場合は、設計を変更せずに自由に最適化できるため、非常にうまくいきます。それを可能にする設計は、一般的に効率の観点から完全になります。


警告:以下は、すべてのサイクルを最大限に活用したい場合にのみ使用することを目的としています。#4を理解し、優れたプロファイラーを身に付けることが不可欠です。また、ヒープ割り当てを最適化しようとするよりも、これらのマトリックスアルゴリズムのメモリアクセスパターンを最適化する方がうまくいく可能性があることにも注意してください。


メモリ割り当てを最適化する必要がある場合は、スレッドごとのメモリプールなどの一般的なもので最適化することを検討してください。たとえば、マトリックスにオプションのアロケータを取り入れることもできますが、ここではオプションを強調し、最初に簡単なアロケータの実装で正確さを強調します。

言い換えると:

各関数内でM1(n、p)を宣言するか、main()で一度だけ宣言し、各関数がスクラップスペースとして使用できる一種のバケットとして各関数に渡す方がよいでしょう。

先に進み、各関数で一時としてM1を作成します。中間結果を計算するためだけにクライアントに意味のないマトリックスを作成するようにクライアントに要求することは避けてください。これは、インターフェースを設計するときに実行しないように努めるべき最適化の詳細を公開することになります(クライアントが知る必要のないすべての詳細を非表示にします)。

代わりに、オプションのアロケータのように、これらの一時的なものの作成を加速するオプションが絶対に必要な場合は、より一般的な概念に焦点を合わせてください。これは、次のような実用的な設計に適合しますstd::set

std::set<int, std::less<int>, MyFastAllocator<int>> s; // <-- okay

ほとんどの人はそうしますが:

std::set<int> s;

あなたの場合、それは単に次のようになります:M1 my_matrix(n、p、alloc);

微妙な違いですが、アロケータはキャッシュされた行列よりもはるかに一般的な概念であり、関数が結果をより高速に計算するために必要なキャッシュの一種であることを除けば、クライアントにとって意味がありません。一般的なアロケータである必要はないことに注意してください。これは、マトリックスコンストラクターに渡される、事前に割り当てられたマトリックスバッファーである可能性がありますが、概念的には、クライアントにとって少し不透明なものであるという事実のために、それを分離することをお勧めします。

さらに、この一時マトリックスオブジェクトを作成するには、スレッド間で共有しないように注意する必要があります。これは、最適化ルートを実行する場合に概念を少し一般化する必要があるもう1つの理由です。マトリックスアロケータのようなもう少し一般的なものは、スレッドセーフを考慮に入れるか、少なくとも、別のアロケータがすべきことを設計によってさらに強調することができるからです。スレッドごとに作成されますが、生のマトリックスオブジェクトはおそらく作成できません。


上記は、インターフェースの品質を何よりも重視する場合にのみ役立ちます。そうでない場合は、アロケータを作成するよりもはるかに簡単なので、Matthieuのアドバイスに従うことをお勧めしますが、どちらも高速バージョンをオプションにすることを強調しています。

于 2012-03-02T08:06:12.543 に答える
1

まず、関数内でマトリックスを定義してみてください。それは間違いなくより良いデザインの選択です。ただし、パフォーマンスが低下した場合は、関数がスレッドセーフではなくなったことを念頭に置いておく限り、「参照ごとにバッファを渡す」ことは問題ないと思います。スレッドを使用する場合は、各スレッドに独自のバッファーが必要です。

于 2012-03-02T07:47:29.850 に答える
1

特に、それを使用する関数をチェーンする必要がある場合は、外部から提供されるバッファーを必要とするというパフォーマンスの点で利点があります。

ただし、ユーザーの観点からは、すぐに煩わしくなる可能性があります。

私はよく、C ++では、両方の方法を提供するだけで両方の世界を最大限に活用できるほど単純であることに気付きました。

int compute(Matrix const& argument, Matrix& buffer);

inline int compute(Matrix const& argument) {
  Matrix buffer(argument.width, argument.height);
  return compute(argument, buffer);
}

この非常に単純なラッピングは、コードが1回記述され、2つのわずかに異なるインターフェイスが表示されることを意味します。

より複雑なAPI(をとる)も、引数に対していくつかのサイズ制約を尊重bufferする必要があるため、少し安全性が低くなります。したがって、ユーザーに低速で安全な使用を促すために、高速API(たとえば名前空間の背後)をさらに絶縁することをお勧めします。最初にインターフェースを取り、必要であることが証明された場合にのみ高速のものを試してください。buffer

于 2012-03-02T08:29:01.083 に答える