34

Javaでの比較とスワップのセマンティクスは何ですか? つまり、異なるスレッド間のアトミック整数インスタンスの特定のメモリ位置への順序付きアクセスを保証するだけの比較およびスワップ メソッドですかAtomicInteger、それともメモリ内のすべての位置への順序付きアクセスを保証しますか。つまり、あたかも揮発性であるかのように動作します。 (メモリーフェンス)。

ドキュメントから:

  • weakCompareAndSet変数をアトミックに読み取り、条件付きで書き込みますが、先行発生順序付けは作成しません。そのため、 のターゲット以外の変数の前後の読み取りおよび書き込みに関して保証はありませんweakCompareAndSet
  • compareAndSetなどの他のすべての読み取りおよび更新操作にgetAndIncrementは、揮発性変数の読み取りと書き込みの両方のメモリ効果があります。

compareAndSet揮発性変数であるかのように機能するAPI ドキュメントから明らかです。ただし、weakCompareAndSet特定のメモリの場所を変更するだけです。したがって、そのメモリ位置が単一のプロセッサのキャッシュ専用である場合、weakCompareAndSetは通常の よりもはるかに高速であると考えられますcompareAndSet

これを尋ねているのは、1 から 8 までthreadnumのさまざまなスレッドを実行して次のメソッドのベンチマークを行ったからです (コードは静的にコンパイルされた JVM 言語である Scala で記述されていますが、その意味とバイトコード変換の両方がこの場合の Java のそれ - この短いスニペットは明確なはずです):threadnumtotalwork=1e9

val atomic_cnt = new AtomicInteger(0)
val atomic_tlocal_cnt = new java.lang.ThreadLocal[AtomicInteger] {
  override def initialValue = new AtomicInteger(0)
}

def loop_atomic_tlocal_cas = {
  var i = 0
  val until = totalwork / threadnum
  val acnt = atomic_tlocal_cnt.get
  while (i < until) {
    i += 1
    acnt.compareAndSet(i - 1, i)
  }
  acnt.get + i
}

def loop_atomic_weakcas = {
  var i = 0
  val until = totalwork / threadnum
  val acnt = atomic_cnt
  while (i < until) {
    i += 1
    acnt.weakCompareAndSet(i - 1, i)
  }
  acnt.get + i
}

def loop_atomic_tlocal_weakcas = {
  var i = 0
  val until = totalwork / threadnum
  val acnt = atomic_tlocal_cnt.get
  while (i < until) {
    i += 1
    acnt.weakCompareAndSet(i - 1, i)
  }
  acnt.get + i
}

4 つのデュアル 2.8 GHz コアと 2.67 GHz 4 コア i7 プロセッサを搭載した AMD 上。JVM は Sun Server Hotspot JVM 1.6 です。結果は、パフォーマンスの違いを示していません。

仕様: AMD 8220 4x デュアルコア @ 2.8 GHz

テスト名: loop_atomic_tlocal_cas

  • スレッド番号: 1

実行時間: (最後の 3 つを表示) 7504.562 7502.817 7504.626 (平均 = 7415.637 分 = 7147.628 最大 = 7504.886 )

  • スレッド番号: 2

実行時間: (最後の 3 つを表示) 3751.553 3752.589 3751.519 (平均 = 3713.5513 分 = 3574.708 最大 = 3752.949 )

  • スレッド番号: 4

実行時間: (最後の 3 つを表示) 1890.055 1889.813 1890.047 (平均 = 2065.7207 分 = 1804.652 最大 = 3755.852 )

  • スレッド番号: 8

実行時間: (最後の 3 つを表示) 960.12 989.453 970.842 (平均 = 1058.8776 分 = 940.492 最大 = 1893.127 )


テスト名: loop_atomic_weakcas

  • スレッド番号: 1

実行時間: (最後の 3 つを表示) 7325.425 7057.03 7325.407 (平均 = 7231.8682 分 = 7057.03 最大 = 7325.45 )

  • スレッド番号: 2

実行時間: (最後の 3 つを表示) 3663.21 3665.838 3533.406 (平均 = 3607.2149 分 = 3529.177 最大 = 3665.838 )

  • スレッド番号: 4

実行時間: (最後の 3 つを表示) 3664.163 1831.979 1835.07 (平均 = 2014.2086 分 = 1797.997 最大 = 3664.163 )

  • スレッド番号: 8

実行時間: (最後の 3 つを表示) 940.504 928.467 921.376 (平均 = 943.665 分 = 919.985 最大 = 997.681 )


テスト名: loop_atomic_tlocal_weakcas

  • スレッド番号: 1

実行時間: (最後の 3 つを表示) 7502.876 7502.857 7502.933 (平均 = 7414.8132 分 = 7145.869 最大 = 7502.933 )

  • スレッド番号: 2

実行時間: (最後の 3 つを表示) 3752.623 3751.53 3752.434 (平均 = 3710.1782 分 = 3574.398 最大 = 3752.623 )

  • スレッド番号: 4

実行時間: (最後の 3 つを表示) 1876.723 1881.069 1876.538 (平均 = 4110.4221 分 = 1804.62 最大 = 12467.351 )

  • スレッド番号: 8

実行時間: (最後の 3 つを表示) 959.329 1010.53 969.767 (平均 = 1072.8444 分 = 959.329 最大 = 1880.049 )

仕様: Intel i7 クアッドコア @ 2.67 GHz

テスト名: loop_atomic_tlocal_cas

  • スレッド番号: 1

実行時間: (最後の 3 つを表示) 8138.3175 8130.0044 8130.1535 (平均 = 8119.2888 分 = 8049.6497 最大 = 8150.1950 )

  • スレッド番号: 2

実行時間: (最後の 3 つを表示) 4067.7399 4067.5403 4068.3747 (平均 = 4059.6344 分 = 4026.2739 最大 = 4068.5455 )

  • スレッド番号: 4

実行時間: (最後の 3 つを表示) 2033.4389 2033.2695 2033.2918 (平均 = 2030.5825 分 = 2017.6880 最大 = 2035.0352 )


テスト名: loop_atomic_weakcas

  • スレッド番号: 1

実行時間: (最後の 3 つを表示) 8130.5620 8129.9963 8132.3382 (平均 = 8114.0052 分 = 8042.0742 最大 = 8132.8542 )

  • スレッド番号: 2

実行時間: (最後の 3 つを表示) 4066.9559 4067.0414 4067.2080 (平均 = 4086.0608 分 = 4023.6822 最大 = 4335.1791 )

  • スレッド番号: 4

実行時間: (最後の 3 つを表示) 2034.6084 2169.8127 2034.5625 (平均 = 2047.7025 分 = 2032.8131 最大 = 2169.8127 )


テスト名: loop_atomic_tlocal_weakcas

  • スレッド番号: 1

実行時間: (最後の 3 つを表示) 8132.5267 8132.0299 8132.2415 (平均 = 8114.9328 分 = 8043.3674 最大 = 8134.0418 )

  • スレッド番号: 2

実行時間: (最後の 3 つを表示) 4066.5924 4066.5797 4066.6519 (平均 = 4059.1911 分 = 4025.0703 最大 = 4066.8547 )

  • スレッド番号: 4

実行時間: (最後の 3 つを表示) 2033.2614 2035.5754 2036.9110 (平均 = 2033.2958 分 = 2023.5082 最大 = 2038.8750 )


上記の例のスレッド ローカルが同じキャッシュ ラインで終了する可能性はありますが、通常の CAS とその脆弱なバージョンとの間に目に見えるパフォーマンスの違いはないように思えます。

これは、実際には、弱い比較とスワップが本格的なメモリ フェンスとして機能する、つまり、揮発性の変数であるかのように機能することを意味する可能性があります。

質問: この観察は正しいですか? また、弱い比較と設定が実際に高速な既知のアーキテクチャまたは Java ディストリビューションはありますか? そうでない場合、そもそも弱い CAS を使用する利点は何ですか?

4

3 に答える 3

33

「アトミック・コンペア・アンド・スワップ」のx86命令はですLOCK CMPXCHG。この命令は、完全なメモリフェンスを作成します。

メモリフェンスを作成せずにこのジョブを実行する命令はないため、との両方が完全なメモリフェンスにcompareAndSetマップweakCompareAndSetされ、実行される可能性が非常に高くなります。LOCK CMPXCHG

しかし、それはx86の場合であり、他のアーキテクチャー(x86の将来のバリアントを含む)は異なる動作をする可能性があります。

于 2010-11-15T10:06:52.017 に答える
33

JVMの実装によっては、弱い比較とスワップが完全な揮発性変数として機能する可能性があります。実際、特定のアーキテクチャで、通常の CAS よりも著しくパフォーマンスの高い方法で弱い CAS を実装することができなくても、私は驚かないでしょう。これらのアーキテクチャでは、弱い CAS が完全な CAS とまったく同じように実装されている可能性があります。または、単純に、弱い CAS を特に高速にするために JVM があまり最適化されていない可能性があります。現在の実装では、実装が速いため完全な CAS を呼び出すだけであり、将来のバージョンではこれが改善される予定です。

JLS は、脆弱な CAS は事前発生の関係を確立しないと単純に述べているため、それによって引き起こされる変更が他のスレッドで表示されるという保証はありません。この場合に得られるのは、compare-and-set 操作がアトミックであるという保証だけですが、(潜在的に) 新しい値の可視性についての保証はありません。これは、表示されないことを保証することと同じではないため、テストはこれと一致しています。

一般に、実験を通じて並行性関連の動作について何らかの結論を下すことは避けてください。考慮すべき変数が非常に多いため、JLS が正しいと保証する内容に従わない場合、プログラムはいつでも壊れる可能性があります (おそらく、別のアーキテクチャでは、おそらくわずかなエラーによって引き起こされるより積極的な最適化の下で) コードのレイアウトの変更、おそらくまだ存在しない JVM の将来のビルドなど)。実験で「機能する」ことが示されているため、保証されていないと述べられているものを回避できると仮定する理由は決してありません.

于 2010-11-15T10:05:26.893 に答える
7

weakCompareAndSwap高速であるとは限りません。高速化が許可されているだけです。OpenJDK のオープンソース コードを見て、一部の賢い人々がこのアクセス許可で何をすることにしたかを確認できます。

つまり、どちらもワンライナーとして実装されています

return unsafe.compareAndSwapObject(this, valueOffset, expect, update);

実装がまったく同じであるため、パフォーマンスもまったく同じです。(少なくとも OpenJDK では)。他の人々は、ハードウェアがすでに「無料」で多くの保証を提供しているため、x86 でこれ以上のことを行うことはできないという事実に言及しています。心配する必要があるのは、ARM のような単純なアーキテクチャだけです。

于 2014-03-14T00:28:39.490 に答える