最新のCPUでの単一割り当てADT指向コードのパフォーマンス


32

単一の割り当てで不変データを操作すると、より多くのメモリが必要になるという明らかな効果があります。これは、常に新しい値を作成しているためです(ただし、隠れたコンパイラーはポインタートリックを実行して問題を軽減します)。

しかし、CPU(具体的にはそのメモリコントローラー)がメモリが(それほど)変化していないという事実を利用できるという点で、パフォーマンスの低下よりもパフォーマンスの低下を上回ることを何度か耳にしました。

私は誰かがこれが本当であるかどうか(またはそうでない場合)に光を当てることを望んでいました。

別のポストにコメントそれがいることを言及した抽象データ型(ADTのは)私は、さらに好奇心作られたこれに関係しています、どのようにADTは、具体的にメモリとCPUの取引方法に影響しますか?ただし、これは別ですが、ほとんどの場合、言語の純度がCPUおよびそのキャッシュなどのパフォーマンスに必ず影響することに興味があります。


2
これは、読者がスナップショットをアトミックに取得し、それを読んでいる間は変化しないという知識で安全であるマルチスレッドで主に役立ちます
ラチェットフリーク

@ratchetfreakプログラミングの観点からは、コードの安全性がより保証されると思いますが、私の好奇心は、CPUのメモリコントローラーと、この動作がそれに対して重要である(またはそうでない場合)ことについてです。メモリコントローラーの方が効率的であると言っていた手がいっぱいあり、低レベルの詳細については、これが本当かどうか、またはどのように言えるのかを十分に知りません。
ジミー・ホッファ

たとえそれが真実であったとしても、メモリの変更を少なくすることが不変性のベストセールスポイントであるとは思わないでしょう。結局のところ、メモリは変更する必要があり、CPUとメモリマネージャは長年にわたってかなり上手くなりました。
ラインヘンリッヒス

1
また、不変の構造体を使用する場合、メモリの効率は必ずしもコンパイラの最適化に依存する必要はないことを指摘したいと思います。この例ではlet a = [1,2,3] in let b = 0:a in (a, b, (-1):c)共有メモリ要件を減少しますが、の定義に依存(:)して[]ではなく、コンパイラ。おもう?これについてはわかりません。

回答:


28

CPU(具体的にはメモリコントローラー)は、メモリが変更されていないという事実を活用できます。

利点は、この事実により、コンパイラーがデータにアクセスするときにmembar命令を使用する必要がなくなることです。

メモリバリアは、membar、メモリフェンス、またはフェンス命令とも呼ばれ、バリア命令の一種であり、中央演算処理装置(CPU)またはコンパイラがバリア命令の前後に発行されるメモリ操作に順序付け制約を適用します。これは通常、特定の操作がバリアの前に実行され、他の操作がその後に実行されることが保証されることを意味します。

最新のCPUのほとんどはパフォーマンスの最適化を採用しているため、メモリのバリアが必要になります。このメモリ操作(ロードとストア)の並べ替えは、通常、単一の実行スレッド内では気付かれませんが、慎重に制御されない限り、並行プログラムおよびデバイスドライバーで予期しない動作を引き起こす可能性があります...


マルチコアCPUで異なるスレッドからデータにアクセスすると、次のようになります:異なるスレッドが異なるコアで実行され、それぞれ独自の(コアに対してローカルな)キャッシュ-グローバルキャッシュのコピーを使用します。

データが可変であり、プログラマーが異なるスレッド間で一貫性を保つ必要がある場合、一貫性を保証するための対策を講じる必要があります。プログラマーにとって、これは特定のスレッドのデータにアクセス(読み取りなど)するときに同期構造を使用することを意味します。

コンパイラの場合、コード内の同期コンストラクトは、他のコアでのキャッシュを保証するために、コアの1つでデータのコピーに加えられた変更が適切に伝播(「公開」)されるように、membar命令を挿入する必要があることを意味します同じ(最新の)コピーがあります。

多少単純化するため、下記のメモを参照してください。membarのマルチコアプロセッサで起こることは次のとおりです。

  1. すべてのコアは処理を停止します -キャッシュへの誤った書き込みを避けるため。
  2. ローカルキャッシュに加えられたすべての更新は、グローバルキャッシュに書き戻されます-グローバルキャッシュに最新のデータが含まれるようにします。これには時間がかかります。
  3. 更新されたデータは、グローバルキャッシュからローカルキャッシュに書き戻されます-ローカルキャッシュに最新のデータが含まれるようにします。これには時間がかかります。
  4. すべてのコアが実行を再開します。

ご覧のとおり、グローバルキャッシュとローカルキャッシュの間でデータがコピーされている間すべてのコアは何もしていません。これは、可変データが適切に同期されるようにするために必要です(スレッドセーフ)。4つのコアがある場合、4つすべてが停止し、キャッシュの同期中に待機します。8個ある場合、8個すべてが停止します。16がある場合...これらのいずれかで必要な処理を待機している間、15のコアがまったく何も実行していないことになります。

それでは、データが不変の場合に何が起こるか見てみましょう。どのスレッドがそれにアクセスしても、同じであることが保証されます。プログラマーにとって、これは、特定のスレッドのデータにアクセス(読み取り)するときに同期構造を挿入する必要ないことを意味します。

コンパイラの場合、これはmembar命令を挿入する必要ないことを意味します

その結果、データへのアクセスは、グローバルキャッシュとローカルキャッシュ間でデータが書き込まれている間、コアを停止して待機する必要がありません。これは、メモリが変化しないという事実の利点です


上記の説明をいくぶん単純化するとパイプラインなど、データが可変であるというより複雑な悪影響がなくなることに注意してください。必要な順序を保証するために、CPUはデータ変更の影響を受けるパイルラインを無効にする必要があります。これはパフォーマンスの低下につながります。これがすべてのパイプラインの単純な(したがって信頼できる:)無効化によって実装される場合、マイナスの影響はさらに増幅されます。



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.