2012-03-02 11 views
2

多くの小さな関数を持つC++コードがあると仮定します。通常、中間計算の結果を格納するために、実行時にn、pの行列float M1(n、p)が必要です。各関数はM1のすべての行を上書きするだけなので、宣言するためにM1を初期化します)。C++ベストプラクティスのマトリックス作成の破壊?

この理由の1つは、各関数が変更できない元のデータ行列で機能するため、「別の場所」で多くの操作(並べ替え、無効化、球形化)を行う必要があることです。

一時的なM1(n、p)を各関数内で作成するか、main()で一度だけ作成し、各関数に渡すことをお勧めします。スクラップスペース?

nおよびpは、nについては適度に大きい[10^2-10^4]であり、pについては[5-100]であることが多い。

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

ベスト、

+0

があります。メモリの割り当ては高価です。ローカルのallocsで開始し、allocsが高価すぎる場合は変更してください。 – Anycorn

+1

@Anycornのメモリ割り当ては、おそらく500以上の値にアクセスするよりも安価で、百万の値にアクセスするよりも安いです。 –

+0

@ JamesKanze +1ヒープ割り当ての相対効率は、一般的にこれらの操作に比べて些細なことになります。最適化ルートを完全に提案しないように投稿を編集することを考えています。 – stinky472

答えて

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

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

ここでの目標は、あまりにも多くの既存のコードを変更することなく、余計なものとして最適化するための呼吸スペースを与えるマトリックスの作成ポリシー(アロケータまたはその他の手段による)を分離することです。関与する関数の実装の詳細だけを変更するか、行列クラスの実装のみを変更することで可能ならば、デザインを変更せずに自由に最適化することができます。それを可能にする任意の設計は、一般に、効率の観点から完全になるであろう。


警告:あなたは本当にすべてのサイクルを最大限に絞るしたい場合にのみ意図されて、次の。 #4を理解し、自分自身に良いプロファイラを得ることは不可欠です。また、ヒープ割り当てを最適化しようとするよりも、これらの行列アルゴリズムのメモリアクセスパターンを最適化するほうが、おそらくもっとうまくいくでしょう。


メモリ割り当てを最適化する必要がある場合は、スレッドごとのメモリプールのような一般的な方法で最適化することを検討してください。例えば、あなたのマトリックスをオプションのアロケータに入れてもいいかもしれませんが、私はここではオプションで強調しています。そして、私は、まず些細なアロケータの実装で正確さを強調したいと思います。すなわち

M1(N、P)各関数内、または むしろ一度、(主にすべてのために)を宣言し 、各機能に渡すためのより良いやり方であります各機能がスクラップスペースとして使用できるバケットの一種。

各機能で一時的にM1を作成します。仲介の結果を計算するためにだけ意味のない行列を作ることをクライアントに要求することを避けるようにしてください。それは、(クライアントが知っているべきではない)細部を隠すために、インタフェースを設計するときにやらなければならないことであるべき最適化の詳細を明らかにするでしょう。

オプションのアロケータのように、これらの一時的なオブジェクトの作成を高速化する必要がある場合は、より一般的な概念に焦点を当ててください。これはstd::setとのような実用的なデザインでフィット:

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

、ほとんどの人はちょうど行うにもかかわらず:あなたのケースでは

std::set<int> s; 

、それは単に次のようになります。 M1のmy_matrix(N、P、アロケーション) ;

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

さらに、この一時的な行列オブジェクトを構築するには、スレッド間で共有しないように注意する必要があります。これは、最適化ルートを実行するとコンセプトを少し一般化したい別の理由です。マトリックスアロケータのようなもっと一般的なものは、スレッドの安全性を考慮に入れることができます。スレッドごとに作成されますが、生の行列オブジェクトはおそらく作成できません。


上記は、インターフェイスの品質を最も重要視する場合にのみ役立ちます。そうでない場合は、アロケータを作成するよりもはるかに単純なのでMatthieuのアドバイスをお勧めしますが、アクセラレータ版をオプションのにすることを重視しています。

+1

誰もが常に標準的なアロケータに勝てると思っています。あなたはstd :: allocatorの実装者がそれを考えているとは思わない? – CashCow

+0

@CashCow標準のアロケータは非常に一般的です。例えば、割り当てられたブロックをただちに解放すると仮定します。私は実際に単純なスタックベースのメモリプールアロケータを作った。私たちのプラットフォームでは約400を取る傾向がある標準アロケータとは対照的に、平均4サイクルでメモリを割り当てます(一般的なケースは1つのポインタをインクリメントすることになります)。私の特別なトリック:私は割り当て解除のメモリを解放しません!通常、アロケータにはパージ関数があることを除いて、リークフェストです。しかし、それは危険です。使用するのはレイ・トレーサの最も重要な部分です。 – stinky472

+1

@CashCow標準アロケータの一般性と効率性の両方にマッチするので、確かに非実用的な目標だと思います。しかし、一般性、安全性、または断片化の懸念を減らすというリスクを前提にすれば、スピードは簡単に上回りますが、これらの領域のうちの1つ以上にはある程度のコストがかかります。 – stinky472

1

まず、関数内でマトリックスを定義してみます。それは間違いなく良い設計選択です。しかし、パフォーマンス上の損失を得ることができない場合は、関数がスレッドセーフではないことに留意している限り、「参照渡しバッファ」は問題ありません。いずれの時点でもスレッドを使用している場合、各スレッドには独自のバッファが必要です。

2

早すぎる最適化を使用しないでください。適切かつ適切に動作するものを作成し、遅く表示された場合は後で最適化します。

(ところで、私はstackoverflowが正しい場所だとは思わない)

実際には、大規模なマトリックス上でアプリケーションを高速化したい場合は、並行性を使用することがあなたのソリューションになります。並行性を使用している場合、大きなグローバル・マトリックスが1つあれば、はるかに大きな問題になる可能性があります。

本質的に、メモリを持っていても、一度に複数の計算を行うことはできません。

あなたのマトリックスのデザインが最適である必要があります。私たちはこのデザインを見なければならないでしょう。

したがって、私は大体あなたのコードで言います。いいえ、大きなグローバルな行列を作成しないでください。なぜなら、あなたがそれをしたいことに対して間違っているからです。

+0

:>アドバイスをいただきありがとうございます。この場合、主関数は多数の独立した入力行列X_ {1} ... X_ {k}で繰り返されなければならないので、並行処理は関数iよりも高いレベルで行われますこれらのX_ {k}のうちの1つのためのものです[私はそれが明確であることを望みます - 私に知らせて、それを言い返すならば。 – user189035

+1

もちろん、プロセスの複数のインスタンスを実行させることで同時実行性を実現することができます。間違った領域で最適化する設計を自分自身に与えることで、あるプロセス内でそれを排除するべきではありません。 – CashCow

+0

はい私はあなたの意見を理解し、それは一般に完全に有効です。私は文脈に固有の情報を与えたいと思っていました。 – user189035

1

外部から供給されるバッファを必要とする場合、特にそれを使用する機能をチェーン化する必要がある場合は、パフォーマンス面でメリットがあります。

しかし、ユーザーの観点からは、すぐに迷惑になることがあります。

私は、多くの場合、それは単に両方の方法をofferringことにより、両方の長所を得るためにC++で十分に簡単だということを発見した

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

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

この非常に簡単なラッピングは、コードを一度書かれていることを意味し、わずかに異なる2つのインタフェースが提示される。

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

関連する問題