Ответ 1
Как обычно, на такие вопросы можно быстро ответить, изучив сгенерированный код. JMH предоставляет вам -prof perfasm
в Linux и -prof xperfasm
в Windows. Если вы запустите тест на JDK 8u40, вы увидите (обратите внимание, что я использовал -bm avgt -tu ns
, чтобы сделать оценки более понятными):
Benchmark Mode Cnt Score Error Units
ACB.SystemArrayCopy avgt 25 13.294 ± 0.052 ns/op
ACB.SystemArrayCopy_ConstantSize avgt 25 16.413 ± 0.080 ns/op
Почему эти тесты работают по-другому? Пусть сначала -prof perfnorm
рассеется (я уронил строки, которые не имеют значения):
Benchmark Mode Cnt Score Error Units
ACB.SAC avgt 25 13.466 ± 0.070 ns/op
ACB.SAC:·CPI avgt 5 0.602 ± 0.025 #/op
ACB.SAC:·L1-dcache-load-misses avgt 5 2.346 ± 0.239 #/op
ACB.SAC:·L1-dcache-loads avgt 5 24.756 ± 1.438 #/op
ACB.SAC:·L1-dcache-store-misses avgt 5 2.404 ± 0.129 #/op
ACB.SAC:·L1-dcache-stores avgt 5 14.929 ± 0.230 #/op
ACB.SAC:·LLC-loads avgt 5 2.151 ± 0.217 #/op
ACB.SAC:·branches avgt 5 17.795 ± 1.003 #/op
ACB.SAC:·cycles avgt 5 56.677 ± 3.187 #/op
ACB.SAC:·instructions avgt 5 94.145 ± 6.442 #/op
ACB.SAC_ConstantSize avgt 25 16.447 ± 0.084 ns/op
ACB.SAC_ConstantSize:·CPI avgt 5 0.637 ± 0.016 #/op
ACB.SAC_ConstantSize:·L1-dcache-load-misses avgt 5 2.357 ± 0.206 #/op
ACB.SAC_ConstantSize:·L1-dcache-loads avgt 5 25.611 ± 1.482 #/op
ACB.SAC_ConstantSize:·L1-dcache-store-misses avgt 5 2.368 ± 0.123 #/op
ACB.SAC_ConstantSize:·L1-dcache-stores avgt 5 25.593 ± 1.610 #/op
ACB.SAC_ConstantSize:·LLC-loads avgt 5 1.050 ± 0.038 #/op
ACB.SAC_ConstantSize:·branches avgt 5 17.853 ± 0.697 #/op
ACB.SAC_ConstantSize:·cycles avgt 5 66.680 ± 2.049 #/op
ACB.SAC_ConstantSize:·instructions avgt 5 104.759 ± 4.831 #/op
Итак, ConstantSize
каким-то образом делает больше L1-dcache-магазинов, но один меньше LLC-load. Hm, так что то, что мы ищем, больше магазинов в постоянном случае. -prof perfasm
удобно выделяет горячие детали в сборке:
default
:
4.32% 6.36% 0x00007f7714bda2dc: movq $0x1,(%rax) ; alloc
0.09% 0.04% 0x00007f7714bda2e3: prefetchnta 0x100(%r9)
2.95% 1.48% 0x00007f7714bda2eb: movl $0xf80022a9,0x8(%rax)
0.38% 0.18% 0x00007f7714bda2f2: mov %r11d,0xc(%rax)
1.56% 3.02% 0x00007f7714bda2f6: prefetchnta 0x140(%r9)
4.73% 2.71% 0x00007f7714bda2fe: prefetchnta 0x180(%r9)
ConstantSize
:
0.58% 1.22% 0x00007facf921132b: movq $0x1,(%r14) ; alloc
0.84% 0.72% 0x00007facf9211332: prefetchnta 0xc0(%r10)
0.11% 0.13% 0x00007facf921133a: movl $0xf80022a9,0x8(%r14)
0.21% 0.68% 0x00007facf9211342: prefetchnta 0x100(%r10)
0.50% 0.87% 0x00007facf921134a: movl $0x20,0xc(%r14)
0.53% 0.82% 0x00007facf9211352: mov $0x10,%ecx
0.04% 0.14% 0x00007facf9211357: xor %rax,%rax
0.34% 0.76% 0x00007facf921135a: shl $0x3,%rcx
0.50% 1.17% 0x00007facf921135e: rex.W rep stos %al,%es:(%rdi) ; zeroing
29.49% 52.09% 0x00007facf9211361: prefetchnta 0x140(%r10)
1.03% 0.53% 0x00007facf9211369: prefetchnta 0x180(%r10)
Таким образом, этот пристойный rex.W rep stos %al,%es:(%rdi)
потребляет значительное время. Это обнуляет вновь выделенный массив. В тесте ConstantSize
JVM не может коррелировать с тем, что вы переписываете весь целевой массив, и поэтому он должен был предварительно обнулить его перед погружением в фактическую копию массива.
Если вы посмотрите на сгенерированный код на JDK 9b82 (последний доступный), вы увидите, что он сбрасывает оба шаблона в необнужденной копии, как вы можете видеть с помощью -prof perfasm
, а также можете подтвердить с помощью -prof perfnorm
Benchmark Mode Cnt Score Error Units
ACB.SAC avgt 50 14.156 ± 0.492 ns/op
ACB.SAC:·CPI avgt 5 0.612 ± 0.144 #/op
ACB.SAC:·L1-dcache-load-misses avgt 5 2.363 ± 0.341 #/op
ACB.SAC:·L1-dcache-loads avgt 5 28.350 ± 2.181 #/op
ACB.SAC:·L1-dcache-store-misses avgt 5 2.287 ± 0.607 #/op
ACB.SAC:·L1-dcache-stores avgt 5 16.922 ± 3.402 #/op
ACB.SAC:·branches avgt 5 21.242 ± 5.914 #/op
ACB.SAC:·cycles avgt 5 67.168 ± 20.950 #/op
ACB.SAC:·instructions avgt 5 109.931 ± 35.905 #/op
ACB.SAC_ConstantSize avgt 50 13.763 ± 0.067 ns/op
ACB.SAC_ConstantSize:·CPI avgt 5 0.625 ± 0.024 #/op
ACB.SAC_ConstantSize:·L1-dcache-load-misses avgt 5 2.376 ± 0.214 #/op
ACB.SAC_ConstantSize:·L1-dcache-loads avgt 5 28.285 ± 2.127 #/op
ACB.SAC_ConstantSize:·L1-dcache-store-misses avgt 5 2.335 ± 0.223 #/op
ACB.SAC_ConstantSize:·L1-dcache-stores avgt 5 16.926 ± 1.467 #/op
ACB.SAC_ConstantSize:·branches avgt 5 19.469 ± 0.869 #/op
ACB.SAC_ConstantSize:·cycles avgt 5 62.395 ± 3.898 #/op
ACB.SAC_ConstantSize:·instructions avgt 5 99.891 ± 5.435 #/op
Конечно, все эти нанообъекты для arraycopy восприимчивы к странным изменениям, вызванным выравниванием, в векторизованных копировальных заглушках, но в этой другой (ужасной) истории, что мне не хватает смелости рассказать.