先日、私の同僚は、静的インスタンスをプロセッサ キャッシュ間で共有できないため、静的クラスを使用するとマルチコア システムでパフォーマンスの問題が発生する可能性があると述べました。そうですか?このステートメントを証明するためのベンチマークはありますか? この声明は、(C# を使用した) .Net 開発に関する議論の文脈で作成されたものですが、言語と環境に依存しない問題のように思えます。
コメントありがとうございます。
先日、私の同僚は、静的インスタンスをプロセッサ キャッシュ間で共有できないため、静的クラスを使用するとマルチコア システムでパフォーマンスの問題が発生する可能性があると述べました。そうですか?このステートメントを証明するためのベンチマークはありますか? この声明は、(C# を使用した) .Net 開発に関する議論の文脈で作成されたものですが、言語と環境に依存しない問題のように思えます。
コメントありがとうございます。
私はあなたの同僚にデータまたは少なくとも参照を求めます。
問題は、共有データがある場合は、共有データがあるということです。それが静的クラス、シングルトンなどを通じて公開されるかどうかは、それほど重要ではありません。そもそも共有データが必要ない場合は、とにかく静的クラスがないことを期待しています。
これらすべてに加えて、特定のアプリケーションでは、静的クラスの共有データのプロセッサ キャッシュよりもはるかに大きなボトルネックが存在する可能性があります。
いつものように、まず最も賢明で、読みやすく、保守しやすいコードを書きます。次に、パフォーマンスのボトルネックがあるかどうかを調べ、それに応じて行動します。
複数のスレッドがそのデータに書き込みを行っている場合、キャッシュ スラッシングが発生します (1 つの CPU のキャッシュに書き込むと、他の CPU のキャッシュが無効になります)。あなたの友人は技術的には正しいですが、それがあなたの主要なボトルネックではない可能性が高いので、問題にはなりません。
複数のスレッドがデータを読み取っている場合、あなたの友人は完全に間違っています。
「[a] 静的インスタンスはプロセッサ キャッシュ間で共有できません。そうですか?」
その発言は私にはあまり意味がありません。各プロセッサの専用キャッシュのポイントは、メモリの小さなパッチのプライベート コピーが含まれていることです。そのため、プロセッサがその特定のメモリ領域にのみアクセスする必要があるアルゴリズムを実行している場合、処理を続行する必要はありません。外部メモリにアクセスします。静的クラス内の静的フィールドについて話している場合、それらのフィールドのメモリはすべて、単一のプロセッサ (またはコア) の専用キャッシュに収まる連続したメモリのチャンクに収まる場合があります。ただし、それぞれにキャッシュされた独自のコピーがあります。これは「共有」されていません。それがキャッシュのポイントです。
アルゴリズムのワーキング セットがキャッシュよりも大きい場合、そのキャッシュは無効になります。つまり、アルゴリズムが実行されると、必要なすべての部分が一度にキャッシュに収まらないため、プロセッサが外部メモリからデータをプルすることが繰り返し発生します。しかし、これは一般的な問題であり、特に静的クラスには当てはまりません。
あなたの同僚は実際にはパフォーマンスについてではなく、複数のスレッドが同じデータを読み書きしている場合に正しいロックを適用する必要性について話していたのだろうか?
いかなる種類のロックや同期も使用しない場合、static-vs.-non-static はパフォーマンスに影響しません。
同期を使用している場合、すべてのスレッドが同じロックを取得する必要がある場合に問題が発生する可能性がありますが、これは静的であることの副作用であり、メソッドが静的であることの直接的な結果ではありません。
「仮想マシン」で制御される言語 (.NET、Java など) では、この制御は基盤となる OS に委譲される可能性が高く、さらに BIOS やその他のスケジューリング制御に委譲される可能性があります。そうは言っても、.NET と Java という 2 つの大物では、静的か非静的かはメモリの問題であり、CPU の問題ではありません。
saua の指摘を繰り返しますが、CPU への影響は、静的情報へのアクセスではなく、同期とスレッド制御から生じます。
CPU キャッシュ管理の問題は、静的メソッドだけに限定されません。一度にメモリアドレスを更新できるのは 1 つの CPU だけです。仮想マシンのオブジェクト、特にオブジェクトのフィールドは、メモリ アドレスへのポインタです。したがって、変更可能なオブジェクトがある場合でも、 FooFoo
の呼び出しsetBar(true)
は一度に 1 つの CPU でのみ許可されます。
以上のことから、.NET と Java の要点は、問題があることを証明できるまで、これらの問題に汗を流して時間を費やすべきではないということです。
したがって:
高度にマルチスレッド化されたアプリを設計するときは、細粒度のロックを多数使用するようにしてください。スレッドが 1 つの部分を取得して実行できるようにデータを分割します。うまくいけば、他のスレッドが独自のデータでビジー状態になっているため、それを待つ必要がなくなります。
x86 アーキテクチャはキャッシュ スヌーピングを実装しており、データ キャッシュが同じものをキャッシュした場合に、書き込み時にデータ キャッシュの同期を維持します。すべてのアーキテクチャがハードウェアでそれを行うわけではありません。
たとえそれが本当だとしても、パフォーマンスを改善するためのより良い方法がたくさんあると思います。プロセッサのキャッシングのために、静的をインスタンスに変更することになると、本当に限界に挑戦していることがわかります。