Что такое семантика сравнения и свопинга в Java? А именно, метод сравнения и свопинга AtomicInteger
просто гарантирует упорядоченный доступ между различными потоками к конкретному местоположению памяти экземпляра атомного целого или гарантирует упорядоченный доступ ко всем местоположениям в памяти, то есть он действует так, как если бы он были неустойчивыми (забор памяти).
Из docs:
-
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
- Номер темы: 1
Время выполнения: (показывая последние 3) 7504,562 7502,817 7504,626 (средн. = 7415,637 мин = 7147,628 макс. = 7504,886).
- Номер темы: 2
Время выполнения: (показывая последние 3) 3751,553 3752,589 3751,519 (avg = 3713,5513 мин = 3574,708 max = 3752,949)
- Номер темы: 4
Время выполнения: (показывая последние 3) 1890.055 1889.813 1890.047 (avg = 2065.7207 мин = 1804,652 макс. = 3755,852).
- Номер темы: 8
Время выполнения: (показывая последние 3) 960,12 989,453 970,842 (avg = 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 (avg = 2014,2086 мин = 1797,997 max = 3664,163)
- Номер темы: 8
Время выполнения: (показывая последние 3) 940,504 928,467 921,376 (среднее value = 943,665 мин = 919,985 max = 997,681)
Имя теста: loop_atomic_tlocal_weakcas
- Номер темы: 1
Время выполнения: (показывая последние 3) 7502,876 7502,857 7502,933 (avg = 7414,8132 мин = 7145,869 макс = 7502,933)
- Номер темы: 2
Время выполнения: (показывая последние 3) 3752,623 3751,53 3752,434 (avg = 3710,1778 мин = 3574,398 max = 3752,623)
- Номер темы: 4
Время выполнения: (показывая последние 3) 1876,723 1881,069 1876,538 (avg = 4110,4221 мин = 1804,62 max = 12467,351)
- Номер темы: 8
Время выполнения: (показывая последние 3) 959,329 1010,53 969,767 (avg = 1072,8444 мин = 959,329 макс = 1880,049)
Технические характеристики: Intel i7 с четырьмя ядрами с частотой 2,67 ГГц
Имя теста: 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,377 (среднее value = 4059,6344 мин = 4026,2739 макс = 4068,5455)
- Номер темы: 4
Время выполнения: (показывая последние 3) 2033,4389 2033,2695 2033,2918 (avg = 2030,5825 мин = 2017,6880 max = 2035,0352)
Имя теста: loop_atomic_weakcas
- Номер темы: 1
Время выполнения: (показывая последние 3) 8130,5620 8129.9963 8132.3382 (среднее value = 8114,0052 мин = 8042,0742 макс = 8132,8542)
- Номер темы: 2
Время выполнения: (показывая последние 3) 4066,9559 4067,0440 4067,2080 (avg = 4086,0608 мин = 4023,6822 макс = 4335,1791)
- Номер темы: 4
Время выполнения: (показывая последние 3) 2034.6084 2169,8127 2034,5625 (avg = 2047,7025 мин = 2032,8131 макс = 2169,8127)
Имя теста: loop_atomic_tlocal_weakcas
- Номер темы: 1
Время выполнения: (показывая последние 3) 8132,5267 8132,0299 8132,2415 (avg = 8114,9328 мин = 8043,3674 макс = 8134,0418)
- Номер темы: 2
Время выполнения: (показывая последние 3) 4066,5924 4066,5797 4066,6519 (среднее value = 4059,1911 мин = 4025,0703 макс = 4066,8547)
- Номер темы: 4
Время выполнения: (показывая последние 3) 2033,2614 2035,5754 2036,9110 (среднее value = 2033,2958 мин = 2023,5082 макс = 2038,8750)
Хотя возможно, что локаторы потоков в приведенном выше примере заканчиваются в одних и тех же строках кэша, мне кажется, что нет заметной разницы в производительности между обычным CAS и его слабой версией.
Это может означать, что на самом деле слабое сравнение и своп действует как полноценный забор памяти, т.е. действует так, как если бы это была изменчивая переменная.
Вопрос: Правильно ли это наблюдение? Кроме того, существует ли известная архитектура или дистрибутив Java, для которых слабые сравнения и настройки на самом деле быстрее? Если нет, то в чем преимущество использования слабого CAS в первую очередь?