четверг, 15 октября 2015 г.

О потокобезопасный счетчиках


После посещения javaday 2015 Kharkiv возникла мысль померить скорость работы разных иплементаций потокобезопасного счетчика. Участники забега:
  1. AtomicLong jre7
  2. AtomicLong jre8
  3. Synchronized
  4. RentrantLock nonFair
  5. LongAdder
Код бенчмарка можно посмотреть на гитхабе
Тестирование проводилось на четырехъядерном Intel(R) Core(TM)2 Quad CPU Q6600 2.40GHz
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 Quad CPU    Q6600  @ 2.40GHz
stepping        : 11
microcode       : 0xba
cpu MHz         : 2394.000
cache size      : 4096 KB
physical id     : 0
siblings        : 4
core id         : 0
cpu cores       : 4
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm dtherm tpr_shadow vnmi flexpriority
bugs            :
bogomips        : 4811.07
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual
power management:

processor       : 1
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 Quad CPU    Q6600  @ 2.40GHz
stepping        : 11
microcode       : 0xba
cpu MHz         : 2394.000
cache size      : 4096 KB
physical id     : 0
siblings        : 4
core id         : 2
cpu cores       : 4
apicid          : 2
initial apicid  : 2
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm dtherm tpr_shadow vnmi flexpriority
bugs            :
bogomips        : 4811.07
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual
power management:

processor       : 2
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 Quad CPU    Q6600  @ 2.40GHz
stepping        : 11
microcode       : 0xba
cpu MHz         : 2394.000
cache size      : 4096 KB
physical id     : 0
siblings        : 4
core id         : 1
cpu cores       : 4
apicid          : 1
initial apicid  : 1
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm dtherm tpr_shadow vnmi flexpriority
bugs            :
bogomips        : 4811.07
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual
power management:

processor       : 3
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 Quad CPU    Q6600  @ 2.40GHz
stepping        : 11
microcode       : 0xba
cpu MHz         : 2394.000
cache size      : 4096 KB
physical id     : 0
siblings        : 4
core id         : 3
cpu cores       : 4
apicid          : 3
initial apicid  : 3
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm dtherm tpr_shadow vnmi flexpriority
bugs            :
bogomips        : 4811.07
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual
power management:

Получившиеся результаты:


1. Непонятно ведет себя ReentrantLock. При двух потоках его результат такой же как и при 16.
2. При увеличении кол-ва потоков практически нет разницы между synchronized и AtomicLong в JRE8.
3. AtomicLong в JRE8 был значительно оптимизирован.
4. LongAdder - узкоспециализированный идеально справляющийся с этой задачей.

За счет чего был оптимизирован AtomicLong?
Включим вывод дизассемблированого кода. Для этого:
1. Нужно установить openjdk-hsdis модуль. Это можно сделать или из репозитория или собрать вручную и положить модуль по адресу ${JDK_HOME}/jre/lib/amd64/hsdis-amd64.so
2. -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*AtomicLong.incrementAndGet -XX:PrintAssemblyOptions=intel


Результат для java7:
  0x00007f085d0651d6: mov    r10,QWORD PTR [rsi+0x10]  ;*invokevirtual compareAndSwapLong
                                                ; - java.util.concurrent.atomic.AtomicLong::compareAndSet@9 (line 149)
                                                ; - java.util.concurrent.atomic.AtomicLong::incrementAndGet@12 (line 220)
  0x00007f085d0651da: mov    r8,r10
  0x00007f085d0651dd: add    r8,0x1             ;*ladd
                                                ; - java.util.concurrent.atomic.AtomicLong::incrementAndGet@7 (line 219)
  0x00007f085d0651e1: mov    rax,r10
  0x00007f085d0651e4: lock   cmpxchg QWORD PTR [rsi+0x10],r8
  0x00007f085d0651ea: sete   r11b
  0x00007f085d0651ee: movzx  r11d,r11b          ;*invokevirtual compareAndSwapLong
                                                ; - java.util.concurrent.atomic.AtomicLong::compareAndSet@9 (line 149)
                                                ; - java.util.concurrent.atomic.AtomicLong::incrementAndGet@12 (line 220)
  0x00007f085d0651f2: test   r11d,r11d
  0x00007f085d0651f5: je     0x00007f085d0651d0  ;*ifeq
                                                ; - java.util.concurrent.atomic.AtomicLong::incrementAndGet@15 (line 220)
  0x00007f085d0651f7: jmp    0x00007f085d0651ba
Для java8 все несколько проще:
  0x00007ff8b111ab3e: movabs rax,0x1
  0x00007ff8b111ab48: mov    rdi,rax
  0x00007ff8b111ab4b: lock   xadd QWORD PTR [rsi+0x10],rdi
  0x00007ff8b111ab51: add    rdi,rax
  0x00007ff8b111ab54: mov    rax,rdi
Получается, что в восьмерке все это делается без цикла и практически в одну инструкцию.
Описание инструкций: lock, xadd, cmpxchg.

Комментариев нет:

Отправить комментарий