2012-01-18 16 views
1

メモリへのアクセスに関連して、私は非常に奇妙なパフォーマンスの問題を抱えています。 コードスニペットは:なぜメモリアクセスが遅いのですか?

#include <vector> 
using namespace std; 

vector<int> arrx(M,-1); 
vector< vector<int> > arr(N,arrx); 

... 
for(i=0;i<N;i++){ 
    for(j=0;j<M;j++){ 
    //>>>>>>>>>>>> Part 1 <<<<<<<<<<<<<< 
    // Simple arithmetic operations 
    int n1 = 1 + 2; // does not matter what (actually more complicated) 
    // Integer assignment, without access to array 
    int n2 = n1; 

    //>>>>>>>>>>>> Part 2 <<<<<<<<<<<<<< 
    // This turns out to be most expensive part 
    arr[i][j] = n1; 
    } 
} 

NとM - 10000ほど - 1000のオーダーの一部定数です。 このコード(リリース版)をコンパイルすると、パート2にコメントがあれば約15クロックかかる。この部分では、実行時間は100クロック以上になるので、ほぼ10倍遅くなります。私は割り当て操作が単純な算術操作よりもはるかに安価であることを期待していました。配列を使用しない場合、実際には真です。しかし、その配列では、割り当てははるかに高価なようです。 私は2-Dの代わりに1-D配列も試しました - 同じ結果(2Dの方が明らかに遅いです)。 また、ベクトル<の代わりにint **またはint *を使用しました。< int>>またはベクトル< int> - やはり同じ結果になります。

なぜ私は配列割り当てでこのようなパフォーマンスが低下するのですか?修正できますか?

編集: つ以上の観察:指定されたコードの一部2に、我々は(コメント内の数値)

n1 = arr[i][j]; // 16 clocks 

速度に

arr[i][j] = n1; // 172 clocks 

からの割り当てを変更する場合は上がります。私たちはラインを変更した場合さらに興味深いことに、:

arr[i][j] = n1; // 172 clocks 

arr[i][j] = arr[i][j] * arr[i][j]; // 110 clocks 

の速度も単純な代入 ためのより高いメモリへ/からの読み出しと書き込みのいずれかの違いはありますか?なぜ私はそのような奇妙なパフォーマンスを得ますか?

ありがとうございます!

+0

これは単なる割り当てではありません。 – AJG85

+1

ネストした 'vector'の代わりにフラットな配列を使うと、より良い結果が得られるかもしれません。 'boost :: multiarray'を試してください。 – pmr

+0

であり、セルをメモリ順に処理したい場合があります。 – ysdx

答えて

5

あなたの実際の「パート1」があなたの例よりもはるかに複雑でない限り、ここには驚きはありません。メモリアクセスは基本算術に比べて遅いです。さらに、最適化を使用してコンパイルしている場合は、結果が一度も使用されないため、「パート1」のほとんどまたはすべてが最適化されている可能性があります。

+0

パート1はあまり複雑ではないかもしれませんが、ループ自体はスニペットで示したものよりも複雑です。実際にはいくつかの浮動小数点演算が必要なので、私の期待はかなり高価でなければなりません。しかし、メモリアクセスがさらに複雑になることが判明しました – user938720

+1

@ user938720:浮動小数点は高価だと思いますか? –

6

あなたの仮定は本当に間違っている...

  1. メインメモリへの書き込みは、単純な足し算を行うよりもはるかに遅いです。
  2. 書き込みを行わない場合は、ループが完全に離れて最適化される可能性が高いのは です。
2

最近のコンピュータについては、メモリアクセスがと非常に遅いが算術演算に比べて悲しいことを発見しました。あなたはそれについて何もできません。これは、銅線の電場が光速の約3分の2でしか移動しないためです。

+1

ヒートはなぜ* CPU *クロックが速くなるのを止めましたが、*メモリバス*クロックがより速く停止した理由です。 – zwol

+0

私は50nsのルックアップのために数学を行いました。それは約3.75メートルのワイヤーです。私は光の速度によって制限されていると私は信じることができたと思う。私はそれが近いと分かっていませんでした。それはクレイジーです_。 –

2

ネストされた配列は、50〜500MBの領域のどこかに存在します。このように多くのメモリを書き込むには時間がかかり、賢明なハードウェアメモリキャッシングがそれほど多くの助けになることはありません。さらに、単一のメモリ書込みでさえ、回路基板上のいくつかの銅線を離れてある距離のシリコンの塊に向かわなければならないので、時間がかかる。物理学が勝つ

もっと掘り下げたいのであれば、(あなたのプラットフォームに存在すると仮定して)cachegrindツールを試してみてください。上で使用しているコードでは、多くの最適化が実際には許可されていないことに注意してください。そのデータアクセスパターンには膨大な再利用可能性がありません。

2

簡単な見積もりをしましょう。今日の典型的なCPUクロック速度は、約1〜2GHz(ギガ= 10から9の電力)です。 (実際に)大量に単純化すると、1プロセッサの処理に約1nsかかる(ナノ= 10からマイナス9になる)ことを意味します。 intのような単純な算術演算は、数十のCPUサイクルを要します。

メモリ:典型的なメモリアクセス時間は約50nsです(ここでも十分な詳細情報に入る必要はありません)。

あなたも、非常に最良のシナリオでは、メモリは約5実際に

10の要因によってCPUよりも遅いですが、私は敷物の下で細部の膨大な量を席巻してることがわかり、基本的な考え方がはっきりしていることを願っています興味があれば、周りに書籍(キーワード:キャッシュ、キャッシュミス、データローカリティなど)があります。 This oneの日付は記入されていますが、一般的な説明の説明にはまだまだ良いです。

関連する問題