すべてのプログラマがメモリについて知っておくべきことは何ですか?


164

Ulrich Drepperが、2007年以降のすべてのプログラマーがメモリについて知っておくべきことのどれだけがまだ有効であるのかと思います。また、1.0より新しいバージョンやエラッタも見つかりませんでした。


1
この記事をmobi形式でどこかにダウンロードして、Kindleで簡単に読めるかどうかを誰かが知っていますか?「pdf」は、ズーム/フォーマットに問題があるため、非常に読みにくい
javapowered

1
それはmobiではありませんが、LWNはこの論文を、電話やタブレットで読みやすい一連の記事として掲載しました。1つ目はlwn.net/Articles/250967にあります
Nathan

回答:


111

私が覚えている限り、Drepperの内容はメモリに関する基本的な概念を説明しています。CPUキャッシュのしくみ、物理メモリと仮想メモリの概要、Linuxカーネルによる動物園の扱いなどです。おそらくいくつかの例では古いAPI参照がありますが、それは問題ではありません。基本的な概念の関連性には影響しません。

したがって、基本的なことを説明する本や記事は古くなっているとは言えません。「すべてのプログラマーがメモリについて知っておくべきこと」は一読の価値がありますが、まあ、それは「すべてのプログラマー」のためではないと思います。それはシステム/組み込み/カーネルの人により適しています。


3
ええ、プログラマがSRAMとDRAMがアナログレベルでどのように機能するかを知る必要がある理由は本当にわかりません。それは、プログラムを書くときにはあまり役に立ちません。そして、その知識を本当に必要とする人は、実際のタイミングなどの詳細についてのマニュアルを読む時間をよりよく費やしてください。多分役に立たないかもしれませんが、少なくとも面白いです。
Voo

47
今日では、パフォーマンス==メモリ性能、そう理解メモリがある任意の高パフォーマンスのアプリケーションの中で最も重要なこと。これにより、ゲーム開発、科学計算、財務、データベース、コンパイラ、大規模なデータセットの処理、視覚化、多数のリクエストを処理する必要のあるものに関係するすべての人にとって、紙は不可欠になります...したがって、アプリケーションで作業している場合テキストエディターのように、ほとんどの時間はアイドル状態です。単語の検索、単語のカウント、スペルチェックなどの高速な操作が必要になるまで、紙はまったく面白くありません。
gnzlbg 2013

144

PDF形式のガイドはhttps://www.akkadia.org/drepper/cpumemory.pdfにあります

それはまだ一般的に優れており、強く推奨されています(私は、他のパフォーマンスチューニングの専門家も考えています)。Ulrich(または他の誰か)が2017年の更新を書いたとしたらすばらしいでしょうが、それは多くの作業(たとえば、ベンチマークの再実行)になるでしょう。他のx86パフォーマンスチューニングおよびSSE / asm(およびC / C ++)最適化リンクも参照してください。 タグwiki。(Ulrichの記事はx86固有ではありませんが、彼のベンチマークのほとんど(すべて)はx86ハードウェアにあります。)

DRAMとキャッシュの動作に関する低レベルのハードウェアの詳細はすべて適用されます。DDR4は DDR1 / DDR2(読み取り/書き込みバースト)で説明したのと同じコマンドを使用します。DDR3 / 4の改善は根本的な変更ではありません。私の知る限り、すべてのアーチに依存しないものはまだ一般的に適用されます、例えばAArch64 / ARM32。

シングルスレッド帯域幅に対するメモリ/ L3レイテンシの影響に関する重要な詳細については、この回答レイテンシバインドプラットフォームセクションも参照してください。bandwidth <= max_concurrency / latencyこれは、実際には、Xeonのような最新のマルチコアCPUにおけるシングルスレッド帯域幅の主要なボトルネックです。しかし、クアッドコアのSkylakeデスクトップは、シングルスレッドでDRAM帯域幅の限界に近づく可能性があります。そのリンクには、x86上のNTストアと通常のストアに関する非常に優れた情報があります。 SkylakeがシングルスレッドのメモリスループットでBroadwell-Eよりもはるかに優れているのはなぜですか?要約です。

したがって、他のNUMAノードおよび独自のNUMAノードでのリモートメモリの使用に関する全帯域幅の利用に関する6.8.5の Ulrichの提案は、メモリコントローラーがシングルコアで使用できるよりも広い帯域幅を備えている最新のハードウェアでは逆効果です。おそらく、低レイテンシのスレッド間通信のために同じNUMAノードで複数のメモリを大量に消費するスレッドを実行することには正味の利点があるが、レイテンシの影響を受けない高帯域幅のものにリモートメモリを使用する状況を想像できるでしょう。しかし、これはかなりあいまいです。通常、スレッドをNUMAノード間で分割し、ローカルメモリを使用させるだけです。コアごとの帯域幅は最大同時実行制限のためにレイテンシの影響を受けます(以下を参照)。ただし、1つのソケットのすべてのコアは通常、そのソケットのメモリコントローラーを飽和させるだけではありません。


(通常)ソフトウェアプリフェッチを使用しない

変更された主なものの1つは、ハードウェアのプリフェッチがPentium 4よりもはるかに優れており、かなり大きなストライドまでのストライドアクセスパターンと、一度に複数のストリーム(4kページごとに1つのフォワード/バックワードなど)を認識できることです。 Intelの最適化マニュアルでは、SandybridgeファミリマイクロアーキテクチャーのさまざまなレベルのキャッシュにおけるHWプリフェッチャーの詳細について説明しています。Ivybridge以降では、新しいページでのキャッシュミスを待ってファストスタートをトリガーするのではなく、次のページのハードウェアプリフェッチがあります。AMDの最適化マニュアルには同様のものがいくつかあると思います。Intelのマニュアルにも古いアドバイスがたくさんあることに注意してください。その一部はP4にのみ有効です。Sandybridge固有のセクションはもちろんSnBには正確ですが、たとえばHSWでマイクロヒューズ付きuopsの非ラミネートが変更され、マニュアルにはそれが記載されていません

最近の通常のアドバイスは、古いコードからすべてのSWプリフェッチを削除し、プロファイリングがキャッシュミスを示している場合にのみそれを元に戻すことを検討することです(そしてメモリ帯域幅を飽和させていません)。バイナリ検索の次のステップの両側をプリフェッチすることは、依然として役立ちます。たとえば、次に見る要素を決定したら、1/4および3/4要素をプリフェッチして、ロード/チェックの中央と並行してロードできるようにします。

別のプリフェッチスレッド(6.3.4)を使用するという提案は完全に廃止されたと思います。これはPentium 4でのみ有効でした。P4にはハイパースレッディング(1つの物理コアを共有する2つの論理コア)がありましたが、十分なトレースキャッシュ(および/または順不同の実行リソース)を使用して、同じコアで2つの完全な計算スレッドを実行してスループットを得る。しかし、最近のCPU(SandybridgeファミリとRyzen)はより強力であり、リアルスレッドを実行するか、ハイパースレッディングを使用しない(他の論理コアをアイドル状態にして、ROBをパーティション化する代わりに、ソロスレッドが完全なリソースを持つようにする)必要があります。

ソフトウェアのプリフェッチは常に「壊れやすい」ものでした。スピードアップを実現するための適切なマジックチューニングの数値は、ハードウェアの詳細と、おそらくシステムの負荷によって異なります。早すぎて、デマンドロードの前に追い出されます。遅すぎるし、それは役に立たない。 このブログ記事は、HaswellでSWプリフェッチを使用して問題の非順次部分をプリフェッチする興味深い実験のコード+グラフを示しています。プリフェッチ命令を適切に使用する方法も参照してください。NTのプリフェッチは興味深いですが、L1からの早期の排除により、L2だけでなくL3またはDRAMに至る必要があるため、さらに脆弱になります。あなたは、パフォーマンスの最後の一滴を必要とする、場合することができます特定のマシンのチューン、SW・プリフェッチは、シーケンシャルアクセスのために見て価値があるが、それメモリのボトルネックに近づいている間に十分なALU作業を行うと、速度が低下する可能性があります。


キャッシュラインサイズは64バイトのままです。(L1Dの読み取り/書き込み帯域幅は非常に高く、すべてのL1Dでヒットすると、最新のCPUは1クロックあたり2つのベクトルロード+ 1つのベクトルストアを実行できますキャッシュどのように高速にできるかを参照してください。)AVX512では、ラインサイズ=ベクトル幅、したがって、1つの命令でキャッシュライン全体をロード/保存できます。したがって、すべての誤って配置されたロード/ストアは、256b AVX1 / AVX2の場合、1つおきにではなく、キャッシュラインの境界を越えます。これは、L1Dになかったアレイのループを遅くすることはありません。

アライメントされていないロード命令は、アドレスが実行時にアライメントされている場合はペナルティはありませんが、コンパイラー(特にgcc)は、アライメントの保証について知っている場合、自動ベクトル化の際により優れたコードを作成します。実際には整列されていないopsは一般的に高速ですが、ページ分割は依然として害を及ぼします(ただし、Skylakeの方がはるかに少なく、100サイクルに対して最大11サイクルのレイテンシしかありませんが、それでもスループットが低下します)。


Ulrichが予測したように、最近のすべてのマルチソケットシステムはNUMAです。統合メモリコントローラーが標準です。つまり、外部Northbridgeはありません。しかし、マルチコアCPUが普及しているため、SMPはもはやマルチソケットを意味しません。NehalemからSkylakeまでのIntel CPUは、コア間のコヒーレンシのバックストップとして大規模な包括的 L3キャッシュを使用しています。AMD CPUは異なりますが、詳細についてはあまり明確ではありません。

Skylake-X(AVX512)には包括的なL3がなくなりましたが、実際にすべてのコアにスヌープをブロードキャストせずに、チップ上のどこに(何があれば)キャッシュされているものをチェックできるタグディレクトリがあると思います。 SKXはリングバスはなくメッシュを使用しているため、残念ながら、以前のメニーコアXeonよりもレイテンシが一般的に悪くなっています。

基本的に、メモリ配置の最適化に関するすべてのアドバイスが適用されますが、キャッシュミスや競合を回避できない場合に発生することの詳細はさまざまです。


6.4.2 Atomic ops:CAS再試行ループがハードウェア調停よりも4倍悪いことを示すベンチマークlock addは、おそらく最大の競合ケースを反映しています。しかし、実際のマルチスレッドプログラムでは、同期は最小限に保たれるため(コストが高いため)、競合が少なく、通常、再試行することなくCAS再試行ループが成功します。

C ++ 11 std::atomic fetch_addはにコンパイルされますlock add(またはlock xadd戻り値が使用されている場合)が、locked命令では実行できない処理をCASを使用して行うアルゴリズムは、通常、災害ではありません。同じ場所へのアトミックアクセスと非アトミックアクセスを混在させたくない場合を除き、gccレガシービルトインまたは新しいビルトインの代わりにC ++ 11std::atomicまたはC11を使用してください...stdatomic__sync__atomic

8.1 DWCAS(cmpxchg16b:gccを使ってそれを放出させることができますが、オブジェクトの半分だけの効率的なロードが必要な場合は、醜いunionハックが必要です:c ++ 11 CASでABAカウンターを実装するにはどうすればよいですか?。(DWCASと2つの別々のメモリロケーションのDCASを混同しないでください。DCASのロックフリーアトミックエミュレーションはDWCASでは不可能ですが、トランザクションメモリ(x86 TSXなど)によって可能になります。)

8.2.4トランザクショナルメモリ:数回の誤った起動(リリースされた後、まれにトリガーされるバグのためにマイクロコードの更新によって無効にされる)の後、インテルは後期モデルのBroadwellおよびすべてのSkylake CPUでトランザクショナルメモリを機能させます。デザインはまだデビッド・カンターがハスウェルのために述べたものです。通常のロックを使用する(そしてフォールバックする可能性がある)コードを高速化するためにそれを使用するロック省略方法があります(特にコンテナーのすべての要素に対して単一のロックがあるため、同じクリティカルセクション内の複数のスレッドが衝突しないことがよくあります) )、またはトランザクションを直接認識するコードを記述します。


7.5 Hugepages:hugetlbfsを手動で使用しなくても、匿名の透明なhugepageがLinuxでうまく機能します。2MiBアラインメントで割り当て>> 2MiBを作成します(posix_memalignまたは、aligned_allocが失敗したときに愚かなISO C ++ 17要件を強制しないsize % alignment != 0)。

2MiBに揃えられた匿名の割り当てでは、デフォルトでhugepagesが使用されます。一部のワークロード(たとえば、大きな割り当てを作成した後もしばらく使用し続ける)は
echo always >/sys/kernel/mm/transparent_hugepage/defrag、4kページにフォールバックする代わりに、カーネルが必要に応じて物理メモリをデフラグすることでメリットを得られる場合があります。(カーネルのドキュメントを参照してください)。または、madvise(MADV_HUGEPAGE)大規模な割り当てを行った後で使用することもできます(2MiBの配置が望ましい)。


付録B:Oprofile:Linux perfはほとんど取って代わられましたoprofile。特定のマイクロアーキテクチャに固有の詳細なイベントについては、ocperf.pyラッパーを使用してください。例えば

ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out

それを使用するいくつかの例については、x86のMOVを本当に「無料」にすることができるかを参照してくださいこれをまったく再現できないのはなぜですか?


3
非常に有益な答えとポインタ!これは明らかに投票に値します!
クラフ2018

@Peter Cordes他に読んでみたいガイドや論文はありますか?私はハイパフォーマンスプログラマではありませんが、それについてもっと学びたいと思います。うまくいけば、日々のプログラミングに組み込むことができる実践を取り入れることができます。
user3927312

4
@ user3927312:agner.org/optimizeは、特にx86の低レベルのものに対する最良かつ最も一貫したガイドの1つですが、一般的なアイデアのいくつかは他のISAに適用されます。asmガイドと同様に、Agnerには最適化C ++ PDFがあります。その他のパフォーマンスとCPUアーキテクチャのリンクについては、stackoverflow.com / tags / x86 / infoを参照してください。また、コンパイラーのasm出力を確認する価値がある場合に、コンパイラーがクリティカルループのasmを改善できるようにC ++を最適化する方法についてもいくつか書きました
Peter Cordes

74

私のざっと目を通すと、かなり正確に見えます。注意すべきことの1つは、「統合」メモリコントローラーと「外部」メモリコントローラーの違いに関する部分です。i7シリーズのリリース以来、Intel CPUはすべて統合されており、AMDはAMD64チップが最初にリリースされて以来、統合されたメモリコントローラーを使用しています。

この記事が書かれてから、全体が変わったわけではなく、速度が速くなり、メモリコントローラーがはるかにインテリジェントになりました(i7は、変更をコミットするように感じるまでRAMへの書き込みを遅らせます)が、全体は変わっていません。少なくとも、ソフトウェア開発者が気にかけることはないでしょう。


5
両方を受け入れたいと思います。しかし、私はあなたの投稿に賛成しています。
Framester

5
おそらく、SW開発者に関連する最も大きな変更は、プリフェッチスレッドが悪い考えであることです。CPUは、ハイパースレッディングで2つのフルスレッドを実行するのに十分強力で、はるかに優れたHWプリフェッチを備えています。一般的にはSWプリフェッチは、多くの、特にシーケンシャルアクセスのために、それほど重要。私の答えを見てください。
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.