web-dev-qa-db-ja.com

2D配列をループする最速の方法は?

私はちょうどつまずいた このブログ投稿 。著者は、長方形をループして何かを計算する2つのコードサンプルを示しています(私の推測では、計算コードは単なるプレースホルダーです)。例の1つでは、彼は長方形を垂直にスキャンし、もう1つは水平にスキャンします。次に彼は、2番目が最速であり、すべてのプログラマーがその理由を知っている必要があると言います。今、私はプログラマーであってはなりません。私にはまったく同じように見えるからです。誰かが私にそれを説明できますか?

ありがとう。

31
Alex Turpin

キャッシュコヒーレンス。水平方向にスキャンすると、データがメモリ内で互いに接近するため、キャッシュミスが少なくなり、パフォーマンスが向上します。十分に小さい長方形の場合、これは問題ではありません。

54
Rob Rolnick

答えは受け入れられましたが、それだけではないと思います。

はい、キャッシュは、これらすべての要素をsomeの順序でメモリに格納する必要がある理由の大きな部分です。保存されている順序でインデックスを作成すると、キャッシュミスが少なくなる可能性があります。可能性が高い。

もう1つの問題(多くの回答でも言及されています)は、ほとんどすべてのプロセッサが非常に高速な整数インクリメント命令を持っていることです。それらは通常、非常に速い「この2番目の任意の量を掛けた量の増分」を持っていません。それはあなたが「穀物に対して」索引を付けるときにあなたが求めているものです。

3番目の問題は最適化です。この種のループの最適化には多くの努力と研究が注がれており、適切な順序でインデックスを作成すると、コンパイラーはこれらの最適化の1つを有効にする可能性がはるかに高くなります。

7
T.E.D.

キャッシュが理由ですが、議論の要点を知りたい場合は、U。Drepperによる「すべてのプログラマーがメモリについて知っておくべきこと」を参照してください。

http://people.redhat.com/drepper/cpumemory.pdf

5

前の答えを少し拡張するには:

通常、プログラマーとして、プログラムのアドレス可能なメモリは、0x00000000から0xFFFFFFFFまでのフラットなバイト配列と考えることができます。オペレーティングシステムは、これらのアドレスの一部(たとえば、0x800000000未満のアドレスすべて)を独自に使用するために予約しますが、他のアドレスでも好きなことを行うことができます。これらのメモリ位置はすべてコンピュータのRAMにあり、それらから読み取りまたは書き込みを行う場合は、適切な命令を発行します。

しかし、これは真実ではありません!プロセスメモリの単純なモデルを汚す複雑な問題がたくさんあります。仮想メモリ、スワッピング、およびキャッシュです。

RAMとの通信にはかなり長い時間がかかります。回転するプレートや磁石が含まれていないため、ハードディスクに移動するよりもはるかに高速ですが、標準ではかなり低速です。最新のCPU。したがって、メモリ内の特定の場所から読み取ろうとすると、CPUはその場所をレジスタに読み込んで適切に呼び出すだけでなく、その場所と/および近くの場所の束を読み取ります。 、CPU上に存在し、メインメモリよりもはるかに高速にアクセスできるプロセッサキャッシュに。

これで、コンピューターの動作について、より複雑ですが、より正確なビューが得られました。メモリ内の場所を読み取ろうとすると、最初にプロセッサキャッシュを調べて、その場所の値がすでにそこに格納されているかどうかを確認します。そうである場合は、キャッシュ内の値を使用します。そうでない場合は、メインメモリに長い時間をかけて、値とその隣接メモリのいくつかを取得し、それらをキャッシュに貼り付けて、スペースを確保するために以前そこにあったものの一部を追い出します。

これで、2番目のコードスニペットが最初のコードスニペットよりも速い理由がわかります。 2番目の例では、最初にa[0]b[0]、およびc[0]にアクセスします。これらの各値は、隣接する値、たとえばa[1..7]b[1..7]、およびc[1..7]とともにキャッシュされます。次に、a[1]b[1]、およびc[1]にアクセスすると、それらはすでにキャッシュにあり、すばやく読み取ることができます。最終的にはa[8]に到達し、再びRAMに移動する必要がありますが、8回のうち7回は、不格好な低速の代わりにニースの高速キャッシュメモリを使用していますRAMメモリ。

(では、ab、およびcへのアクセスが互いにキャッシュから追い出されないのはなぜですか?少し複雑ですが、基本的にプロセッサが保存場所を決定しますそのアドレスによってキャッシュ内の特定の値が指定されるため、空間的に互いに近くない3つのオブジェクトが同じ場所にキャッシュされる可能性は低くなります。)

対照的に、lbrandyの投稿の最初のスニペットを検討してください。最初にa[0]b[0]、およびc[0]を読み取り、a[1..7]b[1..7]、およびc[1..7]をキャッシュします。次に、a[width]b[width]、およびc[width]にアクセスします。幅が> = 8であると仮定すると(おそらくそうです。そうでなければ、この種の低レベルの最適化は気になりません)、RAMに移動して、新しいセットをキャッシュする必要があります) a[1]に到達するまでに、他のスペースを確保するためにキャッシュから追い出されている可能性があります。より大きな3つのアレイの珍しいケースではありません。プロセッサキャッシュよりも、/すべての単一読み取り/がキャッシュを見逃し、パフォーマンスを大幅に低下させる可能性があります。

これは、最新のキャッシュ動作に関する非常に高レベルの議論です。より詳細で技術的なものについては、 this は、主題の徹底的でありながら読みやすい扱いのように見えます。

4
David Seiler

ええ、「キャッシュコヒーレンス」...もちろんそれは依存します、あなたは垂直スキャンのためにメモリ割り当てを最適化することができます。従来、ビデオメモリは左から右、上から下に割り当てられていました。昔と同じようにスキャンラインを描画したCRT画面の時代に戻ったと思います。理論的にはこれを変更することもできますが、これはすべて、水平法に固有のものは何もないということです。

1
Paul