Java comparer et de swaps, de la sémantique et de la performance

Qu'est-ce que la sémantique de comparer et d'échanger en Java? À savoir, la comparaison et la méthode d'échange d'un AtomicInteger seulement garantir commandé l'accès entre les différents threads particulier à l'emplacement de mémoire de la atomique en entier instance, ou qu'il ne garantit commandé l'accès à tous les emplacements dans la mémoire, c'est à dire qu'il agit comme si il s'agissait d'un volatile (mémoire de la clôture).

De la docs:

  • weakCompareAndSet atomiquement lit, et à condition écrit une variable, mais ne crée pas de passe-avant de rangements, de sorte que ne fournit aucune garantie à l'égard de précédents ou suivants les lectures et les écritures de toutes les variables autres que la cible de la weakCompareAndSet.
  • compareAndSet et tous les autres lire-et-opérations de mise à jour comme getAndIncrement ont la mémoire des effets de la lecture et de l'écriture des variables volatiles.

Il est évident à partir de la documentation de l'API compareAndSet actes comme si c'était une variable volatile. Cependant, weakCompareAndSet est censé juste changer son emplacement mémoire spécifique. Ainsi, si cet emplacement de la mémoire est exclusif à la cache d'un processeur unique, weakCompareAndSet est censé être beaucoup plus rapide que le compareAndSet.

Je vous pose cette question parce que j'ai comparé les méthodes suivantes en exécutant threadnum des threads différents, variant threadnum de 1 à 8, et ayant totalwork=1e9 (le code est écrit en Scala, un compilés statiquement JVM de la langue, mais à la fois son sens et le bytecode de traduction sont isomorphe à celle de Java dans ce cas - cette information doit être claire):

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
}

sur un AMD avec 4 double 2.8 GHz cœurs, et un 2,67 GHz 4-core i7. La JVM est un Serveur Sun JVM Hotspot 1.6. Les résultats ne montrent aucune différence de performances.

Spécifications: AMD 8220 4x dual-core @ 2.8 GHz

Nom du Test: loop_atomic_tlocal_cas

  • Thread num.: 1

Temps d'exécution: (montrant des 3 derniers)
7504.562 7502.817 7504.626 (moyenne = 7415.637 min = 7147.628 max = 7504.886 )

  • Thread num.: 2

Temps d'exécution: (montrant des 3 derniers)
3751.553 3752.589 3751.519 (moyenne = 3713.5513 min = 3574.708 max = 3752.949 )

  • Thread num.: 4

Temps d'exécution: (montrant des 3 derniers)
1890.055 1889.813 1890.047 (moyenne = 2065.7207 min = 1804.652 max = 3755.852 )

  • Thread num.: 8

Temps d'exécution: (montrant des 3 derniers)
960.12 989.453 970.842 (moyenne = 1058.8776 min = 940.492 max = 1893.127 )


Nom du Test: loop_atomic_weakcas

  • Thread num.: 1

Temps d'exécution: (montrant des 3 derniers)
7325.425 7057.03 7325.407 (moyenne = 7231.8682 min = 7057.03 max = 7325.45 )

  • Thread num.: 2

Temps d'exécution: (montrant des 3 derniers)
3663.21 3665.838 3533.406 (moyenne = 3607.2149 min = 3529.177 max = 3665.838 )

  • Thread num.: 4

Temps d'exécution: (montrant des 3 derniers)
3664.163 1831.979 1835.07 (moyenne = 2014.2086 min = 1797.997 max = 3664.163 )

  • Thread num.: 8

Temps d'exécution: (montrant des 3 derniers)
940.504 928.467 921.376 (moyenne = 943.665 min = 919.985 max = 997.681 )


Nom du Test: loop_atomic_tlocal_weakcas

  • Thread num.: 1

Temps d'exécution: (montrant des 3 derniers)
7502.876 7502.857 7502.933 (moyenne = 7414.8132 min = 7145.869 max = 7502.933 )

  • Thread num.: 2

Temps d'exécution: (montrant des 3 derniers)
3752.623 3751.53 3752.434 (moyenne = 3710.1782 min = 3574.398 max = 3752.623 )

  • Thread num.: 4

Temps d'exécution: (montrant des 3 derniers)
1876.723 1881.069 1876.538 (moyenne = 4110.4221 min = 1804.62 max = 12467.351 )

  • Thread num.: 8

Temps d'exécution: (montrant des 3 derniers)
959.329 1010.53 969.767 (moyenne = 1072.8444 min = 959.329 max = 1880.049 )

Spécifications: Intel core i7 quad-core @ 2.67 GHz

Nom du Test: loop_atomic_tlocal_cas

  • Thread num.: 1

Temps d'exécution: (montrant des 3 derniers)
8138.3175 8130.0044 8130.1535 (moyenne = 8119.2888 min = 8049.6497 max = 8150.1950 )

  • Thread num.: 2

Temps d'exécution: (montrant des 3 derniers)
4067.7399 4067.5403 4068.3747 (moyenne = 4059.6344 min = 4026.2739 max = 4068.5455 )

  • Thread num.: 4

Temps d'exécution: (montrant des 3 derniers)
2033.4389 2033.2695 2033.2918 (moyenne = 2030.5825 min = 2017.6880 max = 2035.0352 )


Nom du Test: loop_atomic_weakcas

  • Thread num.: 1

Temps d'exécution: (montrant des 3 derniers)
8130.5620 8129.9963 8132.3382 (moyenne = 8114.0052 min = 8042.0742 max = 8132.8542 )

  • Thread num.: 2

Temps d'exécution: (montrant des 3 derniers)
4066.9559 4067.0414 4067.2080 (moyenne = 4086.0608 min = 4023.6822 max = 4335.1791 )

  • Thread num.: 4

Temps d'exécution: (montrant des 3 derniers)
2034.6084 2169.8127 2034.5625 (moyenne = 2047.7025 min = 2032.8131 max = 2169.8127 )


Nom du Test: loop_atomic_tlocal_weakcas

  • Thread num.: 1

Temps d'exécution: (montrant des 3 derniers)
8132.5267 8132.0299 8132.2415 (moyenne = 8114.9328 min = 8043.3674 max = 8134.0418 )

  • Thread num.: 2

Temps d'exécution: (montrant des 3 derniers)
4066.5924 4066.5797 4066.6519 (moyenne = 4059.1911 min = 4025.0703 max = 4066.8547 )

  • Thread num.: 4

Temps d'exécution: (montrant des 3 derniers)
2033.2614 2035.5754 2036.9110 (moyenne = 2033.2958 min = 2023.5082 max = 2038.8750 )


Alors qu'il est possible que, au fil les habitants dans l'exemple ci-dessus se retrouvent dans la même lignes de cache, il me semble qu'il est n'observe pas de différence de performances entre les SAE et sa version faible.

Cela pourrait signifier que, en fait, un faible comparer et swap agit comme part entière de la mémoire de la clôture, c'est à dire des actes comme si c'était une variable volatile.

Question: Est-ce que cette observation correcte? Aussi, est-il connu de l'architecture de Java ou de la distribution pour laquelle une faible comparer et l'ensemble est vraiment le plus rapide? Si non, quel est l'avantage de l'utilisation d'une faiblesse de CAS en premier lieu?

  • x86 ne prend pas en charge un non-fortement de la garantie des TAS comme il a VERROUILLAGE de préfixe. Si Faible et Standard AC sont de la même opération.
InformationsquelleAutor axel22 | 2010-11-15