Какова семантика сравнения и обмена в Java?А именно, метод сравнения и замены AtomicInteger
просто гарантирует упорядоченный доступ между различными потоками к конкретной ячейке памяти экземпляра атомарного целочисленного значения или гарантирует упорядоченный доступ ко всем расположениям в памяти, т. Е. Действует так, как если бы онбыли энергозависимы (забор памяти).
Из документов :
weakCompareAndSet
атомно считывает и условно записывает переменную, но не создает никаких событий-перед порядком, поэтому не дает никаких гарантий относительно предыдущих или последующих операций чтения и записи любых переменных, кроме цели weakCompareAndSet
. compareAndSet
и всех других операций чтения и обновления, таких какgetAndIncrement
имеет эффекты памяти для чтения и записи изменяемых переменных.
Из документации API очевидно, что compareAndSet
действует так, как если бы это была переменная переменная.Тем не менее, weakCompareAndSet
должен просто изменить свою конкретную ячейку памяти.Таким образом, если эта область памяти является исключительной для кэша одного процессора, weakCompareAndSet
должен быть намного быстрее, чем обычный compareAndSet
.
Я спрашиваю об этом, потому что я протестировал следующееметоды, запускающие threadnum
различных потоков, варьирующиеся threadnum
от 1 до 8 и имеющие totalwork=1e9
(код написан на Scala, статически скомпилированном языке JVM, но и его значение, и перевод байт-кода изоморфны языку Javaв данном случае - этот короткий фрагмент должен быть понятным):
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
}
на AMD с 4-мя сдвоенными ядрами 2,8 ГГц и 4-ядерным процессором i7 с частотой 2,67 ГГц.JVM - это Sun Server Hotspot JVM 1.6.Результаты не показывают разницы в производительности.
Характеристики: AMD 8220 4x двухъядерный с частотой 2,8 ГГц
Имя теста: loop_atomic_tlocal_cas
Время выполнения: (показаны последние 3) 7504,562 7502,817 7504,626 (средн. = 7415,637 мин. = 7147,628 макс. = 7504,886)
Время выполнения: (показаны последние 3) 3751,553 3752,589 3751,519 (среднее = 3713,5513 мин = 3574,708 макс = 3752,949)
Время выполнения:(показаны последние 3) 1890.055 1889.813 1890.047 (среднее = 2065.7207 мин = 1804.652 макс = 3755.852)
Время выполнения: (показаны последние 3)960.12 989.453 970.842 (среднее = 1058.8776 мин = 940.492 макс = 1893.127)
Имя теста: loop_atomic_weakcas
Время выполнения: (показывает последние 3) 7325,425 7057,03 7325,407 (среднее = 7231,8682 мин = 7057,03 макс = 7325,45)
Время выполнения: (показывает последние 3) 3663,21 3665,838 3533,406 (средн. = 3607,2149 мин. = 3529,177 макс. = 3665,838)
Время выполнения: (показаны последние 3) 3664,163 1831,979 1835,07 (среднее значение = 2014,2086 мин = 1797,997 макс = 3664,163)
Выполнитьвремя: (показывает последние 3) 940,50 928,467 921,376 (среднее = 943,665 мин = 919,985 макс = 997,681)
Имя теста: loop_atomic_tlocal_weakcas
Время выполнения: (показывает последние 3) 7502,876 7502,857 7502,933 (среднее = 7414,8132 мин = 7145,869 макс = 7502,933)
Время выполнения: (показаны последние 3) 3752.623 3751.53 3752.434 (среднее значение = 3710.1782 мин = 3574.398 макс = 3752.623)
Время выполнения:(показаны последние 3) 1876,723 1881,069 1876,538 (среднее = 4110,4221 мин = 1804,62 макс = 12467,351)
Время выполнения: (показаны последние 3)959,329 1010,53 969,767 (средняя = 1072,8444 мин = 959,329 макс = 1880,049)
Характеристики: четырехъядерный процессор Intel i7 @ 2,67 ГГц
Имя теста: loop_atomic_tlocal_cas
Время выполнения: (показаны последние 3)
8138,3175 8130,0044 8130,1535 (средн. = 8119,2888 мин. = 8049,6497 макс. = 8150,1950)
Время выполнения: (показаны последние 3)
4067.7399 4067.5403 4068.3747 (ср = 4059.6344 мин = 4026.2739 макс = 4068.5455)
Время выполнения: (показаны последние 3)
2033,4389 2033,2695 2033,2918 (ср = 2030,5825 мин = 2017,6880 макс = 2035,0352)
Имя теста: loop_atomic_weakcas
Время выполнения: (показаны последние 3)
8130,5620 8129,9963 8132,3382 (среднее = 8114,0052 мин = 8042,0742 макс = 8132,8542)
Время выполнения: (показаны последние 3)
4066,9559 4067,0414 4067,2080 (ср = 4086,0608 мин = 4023,6822 макс = 4335,1791)
Время выполнения: (показаны последние 3)
2034,6084 2169,8127 2034,5625 (ср = 2047,7025 мин = 2032,8131 макс = 2169,8127)
Имя теста: loop_atomic_tlocal_weakcas
Время выполнения: (показаны последние 3)
8132,5267 8132,0299 8132,2415 (средн. = 8114,9328 мин. = 8043,3674 макс. = 8134,0418)
Время выполнения: (показаны последние 3)
4066,5924 4066,5797 4066,6519 (ср = 4059,1911 мин = 4025,0703 макс = 4066,8547)
Время выполнения: (показаны последние 3)
2033,2614 2035,5754 2036,9110 (ср = 2033,2958 мин = 2023,5082 макс = 2038,8750)
Хотя вполне возможно, что локальные потоки в приведенном выше примере окажутся в одних и тех же строках кэша, мне кажется, что между обычным CAS и его слабой версией заметной разницы в производительности нет.
Это может означать, что на самом деле слабое сравнение и свопинг действуют как полноценный забор памяти, то есть действует так, как если бы это была переменная переменная.
Вопрос: Правильно ли это наблюдение? Кроме того, существует ли известная архитектура или дистрибутив Java, для которого слабое сравнение и настройка на самом деле быстрее? Если нет, то в чем преимущество использования слабого CAS?