Javaの擬似共有

817を読みます

次のように疑似シェアのWikipediaの定義は次のとおりです。

In computer science, false sharing is a performance-degrading usage pattern that can arise in systems with distributed, coherent caches at the size of the smallest resource block managed by the caching mechanism. When a system participant attempts to periodically access data that will never be altered by another party, but those data shares a cache block with data that are altered, the caching protocol may force the first participant to reload the whole unit despite a lack of logical necessity. The caching system is unaware of activity within this block and forces the first participant to bear the caching system overhead required by true shared access of a resource. 

これは大体手段:
CPUのキャッシュは複数のスレッドが別の変数を変更する場合、ユニットとしてのキャッシュライン(キャッシュライン)キャッシュであり、そしてこれらの変数は順番に同じキャッシュラインにある互いのパフォーマンスに影響します。例:スレッド1とスレッド2の操作は可変するので、異なる変数であるが、スレッド1および2株スレッドのキャッシュラインは、キャッシュラインが、1キャッシュライン2を修飾2つのスレッド変数をスレッド変数を読み取る1 2変数2が変更されたときに、キャッシュラインの故障、スレッド1は、このようなパフォーマンスの問題が生じるキャッシュミスを引き起こし、メインメモリからの再読み出すべきキャッシュライン内の同じ変数。偽共有ステップのより深い理解のために、我々は、CPUのキャッシュを見てください。

CPU L3キャッシュ

CPU速度は、この問題を解決するために、3つのCPUキャッシュの導入メモリの速度よりもはるかに大きい:L1、L2及びL3から3つのレベル、CPUのL1に最も近い、その後のL2、L3は、L3、CPUから最も遠いですこれは、メインメモリです。速度は、L1> L2> L3>メインメモリです。CPUの近くに容量が小さくなります。もはやメインメモリからロードされた場合、キャッシュを見つけていない順番に3からデータを取得するためのCPU。次のように図です。

 
CPU L3キャッシュ

CPUはキャッシュから最初に、データを読み取るときに、あなたはまだレベル3キャッシュまたはメモリからそれを見つけていない場合は、見つからない場合は、その後、二次キャッシュを見つけます。データの全体量の80%がキャッシュ内で見つけることができることを意味し、おそらく80%程度一般的には、各レベルのキャッシュのヒット率、それのわずか20%は二次キャッシュから必要なデータの総量レベル3キャッシュメモリまたは読み取り、我々は全体のCPUのキャッシュを見ることができる最も重要な部分でキャッシュ・アーキテクチャです。
以下の表に示す消費データ・キャッシュ・ミスのいくつか:

CPUからの これは、CPUサイクル程度かかり それは時間程度かかり
主記憶   60-80nsについて
QPIバス   20nsの概要
L3キャッシュ 40-45cyclesについて 15NSについて
L2キャッシュ 10cyclesについて 3nsの概要
L1キャッシュ 3-4cyclesについて 1nsの概要
登録 1サイクル  

MESIプロトコル

キャッシュラインの状態

CPUキャッシュがキャッシュライン(キャッシュライン)単位であり、MESIプロトコルは、マルチコアプロセッサのキャッシュラインの状態を記述する。MESIプロトコルでは、各キャッシュ・ライン、すなわち4つの状態、があります。

  • M(変形、変更された):ローカル・プロセッサ・キャッシュ・ラインが変更されている、すなわち、ラインが汚れて、その内容とメモリの内容と同じではなく、このローカルキャッシュだけのコピー(排他的)です。
  • E(プロプライエタリ、排他):キャッシュラインと同じメモリ、およびこの行ない他のプロセッサトランザクションの内容。
  • S(共有、共有):キャッシュラインと同じメモリの内容は、他のプロセッサは、キャッシュラインのこのコピーで存在することができるあります。
  • I(無効、無効):キャッシュラインの障害、それを使用することはできません。

下に示すように、E状態のキャッシュライン:

 
MESIプロトコル-E

このときだけコア1アクセスキャッシュラインは、そのキャッシュラインの状態はEで、コア1の排他を表します。

下に示すように、S状態キャッシュライン:

 
MESIプロトコル-S

この時点で、コア1とコア2は、キャッシュラインにアクセスするには、そのキャッシュラインの状態はSは、それが共有状態のキャッシュラインを表しています。

下に示すように、MとI状態キャッシュライン:

 
MESIプロトコル-M

このときCORE1がMであるので、代表的には、キャッシュライン状態がコア2 Iであり、修飾された修飾されたキャッシュラインは、キャッシュラインの状態をCORE1、代表的には、メインメモリから読み出すことに失敗しました。

キャッシュラインの状態遷移

MESIプロトコルでは、キャッシュキャッシュコントローラのそれぞれが独自の読み書きを知っているだけでなく、(スヌープ)を監視するために、他のキャッシュの読み取り操作と書き込み。コアの現在の書き込み動作と他のコアに応じて四つの状態の間で状態の移行各キャッシュ・ライン。MESIプロトコルの状態遷移図次のように

 
MESIプロトコル - 状態遷移
  • 初期:当初、データなしのキャッシュラインがロードされていない、それは私の状態です。
  • ローカル(ローカル書込み)を書く:I状態のキャッシュラインの書き込みデータへのローカル・プロセッサは、キャッシュラインがM.状態になった場合
  • ローカルセンス(現地読む):ローカル・プロセッサはキャッシュラインがI状態にあると記されている場合は、このバッファがそれにデータがないことは明らかです。この時点で2例:(1)他のプロセッサのキャッシュなしのトリップデータは、このデータがキャッシュラインの後にメモリからロードされ、その後、E状態にそれを設定し、彼が他の、唯一の私の家族は、このデータを持っていると述べましたプロセッサはない;(2)他のプロセッサがこのキャッシュラインデータ、S状態これにキャッシュラインの状態を有しています。(注:ローカル・プロセッサの書き込み、その後、M状態のキャッシュラインの場合/読み、状態は変更されません)
  • リモート読み出し(リモート読み取り):必要に応じて、C1キャッシュラインがAにメモリコントローラ(メモリコントローラ)によってその内容を必要とし、C1のキャッシュライン、さらにプロセッサの内容を読み取るために、C2、我々は二つのプロセッサC1とC2があるとC2、対応するキャッシュラインの状態にc2はS.を設定します 設定する前に、あなたがバスからこのメモリデータを取得し、保存する必要があります。
  • 実際の後、むしろ遠隔書き込みよりますが、C2 C1取得データの読み取りが、書き込みをしない:リモート書き込み(リモート書き込み)。書き込みはローカルと見なさでなく、それを行う方法である。このデータc1、のコピーを持っていることができますか?c2は私がデータラインを移動することができない人、それ自身に加えて、他のプロセッサに許可データラインを持っている必要がありますRFO(リクエスト所有者の)要求、対応するキャッシュラインを発行します。リクエストを処理して、私は、性能、消費の多くを書き込みますRFOプロセスを設定しながら、これは、データのセキュリティを保証します。

キャッシュライン

CPUキャッシュは、ユニットに格納されたキャッシュライン(キャッシュライン)です。典型的には、64バイトのキャッシュ・ラインは、それが効果的にメインメモリ内の基準アドレスです。Javaは、バイト長、したがって、キャッシュライン8長い変数型で存在することができる8のタイプです。だから、あなたが長い列を訪問した場合、配列の値がキャッシュにロードされるとき、それはあなたが非常に迅速に配列を通過できるように、余分な負荷別の7になります。確かに、あなたは非常に迅速にメモリの連続ブロックに割り当てられた任意のデータ構造をトラバースすることができます。メモリ内のデータ構造でのあなたの鍵がお互いに(このようなリストとして)隣接していない場合は、あなたがもたらした自由なキャッシュ・ロードの利点を得ることはありませんし、各エントリには、これらのデータ構造で発生する可能性がありますキャッシュミス。図は、CPUのキャッシュラインの模式図です。

 
CPUのキャッシュライン

プロセッサで実行、上図は、プロセッサ・コア2スレッド上の他の実行は、変数の値を更新したいと考えている間、スレッドは、変数Xの更新値を望んCORE1 Y. しかし、これら二つの変数が同じキャッシュラインが頻繁に変化しています。RFO 2つのスレッドがメッセージを送信するターンを取るだろう、所有権の会計処理は、このキャッシュ・ラインでした。コア1は、更新Xを開始するために、所有権をしたときは、対応するキャッシュラインは、私は状態に必要Core2の。Core2の更新Yを開始する所有権を取得した場合には、コア1に対応するキャッシュラインは、私は状態(障害状態)に必要です。このラインデータを読み出すためにニュースの多くをもたらすが、スレッドの必要がある場合だけでなく、RFOの所有権をつかむに変わり、L1およびL2キャッシュは無効なデータですが、良いデータだけL3キャッシュに同期されます。私たちは、以前のものは、L3データは非常にパフォーマンスに影響を与えている読んでいることを知っています。さらに悪いことには、スロット間で読まれ、L3は欠場する必要があり、専用メモリからロードすることができます。

XおよびYは、独立して、スレッドの動作すべき表面上にあり、二つの操作の間には関係がありません。しかし、彼らはキャッシュラインを共有しますが、すべての競技の競合が共有から来ています。

Javaの擬似共有

偽共有を解決するための最も直接的な方法は、例えば、以下VolatileLongは、長い8バイト、8バイト(32ビットシステム)または12バイト(64ビットシステムのJavaオブジェクトヘッダを占有し、(パディング)充填され、デフォルトオープンオブジェクトヘッダ圧縮)16のバイトを開けないでください。64バイトのキャッシュラインが、我々は、6つの長い(6×8 = 48バイト)を充填してもよいです。これは、複数のVolatileLong共有キャッシュラインを避けることができます。

public class VolatileLong {
    private volatile long v; // private long v0, v1, v2, v3, v4, v5 // 去掉注释,开启填充,避免缓存行共享 } 

これは、最もシンプルかつ直接的な方法であり、Javaの8は、よりシンプルなソリューションを導入しました:@Contended注:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE}) public @interface Contended { String value() default ""; } 

競合注釈は自動的に偽の共有を避けるために、このコメントの後に追加する仮想機会を充填型とプロパティで使用することができます。このコメントはJava8のConcurrentHashMap、ForkJoinPoolとスレッドや他のクラスでアプリケーションを持っています。私たちは、偽共有の問題を解決するために、この注釈でのConcurrentHashMapで競合するJava8を使用する方法を見て。以下Java8はConcurrentHashMapのバージョンと言われています。

ConcurrentHashMapの擬似共有ソリューション

操作CounterCellによって算出ConcurrentHashMapのサイズは、ハッシュテーブル内の各ノードはCounterCell、それぞれ対応するノードCounterCellレコードキーと値のペアの数のために使用されます。このように、個々のCounterCell蓄積されたサイズは、それに基づいて計算します。<フォント色=「#FF0000」 > ConcurrentHashMapのアレイに記憶されているCounterCellでは、メモリ内のアレイは連続して格納され、CPUのキャッシュが隣接CounterCellをCounterCell CounterCellだけ長いタイプ値属性ので、形成されてフォルス・シェアリング。</フォント>
のConcurrentHashMapの自動CounterCellが満たされる競合の注釈が付け:

/**
 * Table of counter cells. When non-null, size is a power of 2.
 */
private transient volatile CounterCell[] counterCells; // CounterCell数组,CounterCell在内存中连续 public int size() { long n = sumCount(); return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); } // 计算size时直接对各个CounterCell的value进行累加 final long sumCount() { CounterCell[] as = counterCells; CounterCell a; long sum = baseCount; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) sum += a.value; } } return sum; } // 使用Contended注解自动进行填充避免伪共享 @sun.misc.Contended static final class CounterCell { volatile long value; CounterCell(long x) { value = x; } } 

<フォント色=「## FF0000」 > という注意@sun.misc.Contended注釈が非アクティブなユーザー・クラスパスは、仮想マシンのパラメータによって開かれる必要がある。-XX:-RestrictContended。</ FONT>

概要

  1. CPUキャッシュは、ユニットが作動したキャッシュラインです。偽共有の問題の原因ルートは、異なるコアが同時に同じキャッシュライン上で動作することです。
  2. 偽共有の問題がJava8で充填することによって解決することができる導入@sun.misc.Contended自動的に注釈を。
  3. CPUのキャッシュが制限され、キャッシュの一部を犠牲に入力されますので、必ずしもすべてのシナリオは、解決すべき偽共有の問題です。

 

おすすめ

転載: www.cnblogs.com/zdcsmart/p/12576464.html