Я думал, что будет быстрее создавать напрямую, но на самом деле, добавление циклов занимает только половину времени. Что случилось, что так сильно замедлилось?
Вот код теста
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class Test_newArray {
private static int num = 10000;
private static int length = 10;
@Benchmark
public static int[][] newArray() {
return new int[num][length];
}
@Benchmark
public static int[][] newArray2() {
int[][] temps = new int[num][];
for (int i = 0; i < temps.length; i++) {
temps[i] = new int[length];
}
return temps;
}
}
Результаты теста следующие.
Benchmark Mode Cnt Score Error Units
Test_newArray.newArray avgt 25 289.254 ± 4.982 us/op
Test_newArray.newArray2 avgt 25 114.364 ± 1.446 us/op
Среда тестирования выглядит следующим образом
Версия JMH: 1,21
Версия виртуальной машины: JDK 1.8.0_212, 64-разрядная виртуальная машина OpenJDK, 25.212-b04
В Java существует отдельная инструкция по байт-коду для выделения многомерных массивов - multianewarray
.
newArray
использование сравнительного теста multianewarray
байт-код; newArray2
вызывает простой newarray
в цикле. проблема состоит в том, что HotSpot JVM не имеет никакого быстрого пути <глоток> * глоток> для multianewarray
байт-код. Эта инструкция всегда выполняется во времени выполнения VM. Поэтому выделение не встраивается в скомпилированном коде.
первый сравнительный тест должен заплатить потерю производительности переключения между Java и контекстами Во время выполнения VM. Кроме того, общий код выделения во времени выполнения VM (записанный в C++) столь не оптимизирован как встроенное выделение в скомпилированном коде JIT, просто потому что это универсально , т.е. не оптимизированное для конкретного типа объекта или для конкретного сайта вызова, это выполняет дополнительные проверки на этапе выполнения, и т.д.
Вот результаты профилирования обоих сравнительных тестов с асинхронный профилировщик . Я использовал JDK 11.0.4, но для JDK 8 изображение выглядит подобным.
В первом случае, 99%-е время проведено внутреннее OptoRuntime::multianewarray2_C
- код C++ во времени выполнения VM.
Во втором случае, большая часть графика является зеленой, означая, что прогоны программы главным образом в контексте Java, на самом деле выполняя скомпилированный код JIT оптимизировали специально для данного сравнительного теста.
РЕДАКТИРОВАНИЕ
<глоток> * глоток> Просто для уточнения: в HotSpot multianewarray
не оптимизирован очень хорошо дизайном. Это является довольно дорогостоящим для реализации такой сложной операции в обоих JIT-компиляторах правильно, в то время как преимущества такой оптимизации были бы сомнительны: выделение массивов multidimentional редко является узким местом производительности в типовом приложении.
В примечании в Документы Oracle под multianewarray
инструкция говорится:
может быть более эффективно использовать
newarray
илиanewarray
( В§newarray , В§anewarray ) при создании массива единственного размера.
Далее:
newArray
использование сравнительного теста multianewarray
инструкция по байт-коду.
newArray2
использование сравнительного теста anewarray
инструкция по байт-коду.
И именно это имеет значение. Давайте посмотрим, что статистика получила использование perf
профилировщик Linux.
Для эти newArray
сравнивают самых горячих методов после того, как встраивание будет:
....[Hottest Methods (after inlining)]..............................................................
22.58% libjvm.so MemAllocator::allocate
14.80% libjvm.so ObjArrayAllocator::initialize
12.92% libjvm.so TypeArrayKlass::multi_allocate
10.98% libjvm.so AccessInternal::PostRuntimeDispatch<G1BarrierSet::AccessBarrier<2670710ul, G1BarrierSet>, (AccessInternal::BarrierType)1, 2670710ul>::oop_access_barrier
7.38% libjvm.so ObjArrayKlass::multi_allocate
6.02% libjvm.so MemAllocator::Allocation::notify_allocation_jvmti_sampler
5.84% ld-2.27.so __tls_get_addr
5.66% libjvm.so CollectedHeap::array_allocate
5.39% libjvm.so Klass::check_array_allocation_length
4.76% libc-2.27.so __memset_avx2_unaligned_erms
0.75% libc-2.27.so __memset_avx2_erms
0.38% libjvm.so __tls_get_addr@plt
0.17% libjvm.so memset@plt
0.10% libjvm.so G1ParScanThreadState::copy_to_survivor_space
0.10% [kernel.kallsyms] update_blocked_averages
0.06% [kernel.kallsyms] native_write_msr
0.05% libjvm.so G1ParScanThreadState::trim_queue
0.05% libjvm.so Monitor::lock_without_safepoint_check
0.05% libjvm.so G1FreeCollectionSetTask::G1SerialFreeCollectionSetClosure::do_heap_region
0.05% libjvm.so OtherRegionsTable::occupied
1.92% <...other 288 warm methods...>
....[Distribution by Source]....
87.61% libjvm.so
5.84% ld-2.27.so
5.56% libc-2.27.so
0.92% [kernel.kallsyms]
0.03% perf-27943.map
0.03% [vdso]
0.01% libpthread-2.27.so
................................
100.00% <totals>
И для newArray2
:
....[Hottest Methods (after inlining)]..............................................................
93.45% perf-28023.map [unknown]
0.26% libjvm.so G1ParScanThreadState::copy_to_survivor_space
0.22% [kernel.kallsyms] update_blocked_averages
0.19% libjvm.so OtherRegionsTable::is_empty
0.17% libc-2.27.so __memset_avx2_erms
0.16% libc-2.27.so __memset_avx2_unaligned_erms
0.14% libjvm.so OptoRuntime::new_array_C
0.12% libjvm.so G1ParScanThreadState::trim_queue
0.11% libjvm.so G1FreeCollectionSetTask::G1SerialFreeCollectionSetClosure::do_heap_region
0.11% libjvm.so MemAllocator::allocate_inside_tlab_slow
0.11% libjvm.so ObjArrayAllocator::initialize
0.10% libjvm.so OtherRegionsTable::occupied
0.10% libjvm.so MemAllocator::allocate
0.10% libjvm.so Monitor::lock_without_safepoint_check
0.10% [kernel.kallsyms] rt2800pci_rxdone_tasklet
0.09% libjvm.so G1Allocator::unsafe_max_tlab_alloc
0.08% libjvm.so ThreadLocalAllocBuffer::fill
0.08% ld-2.27.so __tls_get_addr
0.07% libjvm.so G1CollectedHeap::allocate_new_tlab
0.07% libjvm.so TypeArrayKlass::allocate_common
4.15% <...other 411 warm methods...>
....[Distribution by Source]....
93.45% perf-28023.map
4.31% libjvm.so
1.64% [kernel.kallsyms]
0.42% libc-2.27.so
0.08% ld-2.27.so
0.06% [vdso]
0.04% libpthread-2.27.so
................................
100.00% <totals>
, Как мы видим для более медленного newArray
большую часть времени, потрачен в коде jvm (общее количество на 87,61%):
22.58% libjvm.so MemAllocator::allocate
14.80% libjvm.so ObjArrayAllocator::initialize
12.92% libjvm.so TypeArrayKlass::multi_allocate
7.38% libjvm.so ObjArrayKlass::multi_allocate
...
, В то время как newArray2
использование эти OptoRuntime::new_array_C
, проводя намного меньше времени, выделяя память для массивов. Общее время, проведенное в коде jvm, составляет только 4,31%.
Бонусная статистика получила использование perfnorm
профилировщик:
Benchmark Mode Cnt Score Error Units
newArray avgt 4 448.018 ± 80.029 us/op
newArray:CPI avgt 0.359 #/op
newArray:L1-dcache-load-misses avgt 10399.712 #/op
newArray:L1-dcache-loads avgt 1032985.924 #/op
newArray:L1-dcache-stores avgt 590756.905 #/op
newArray:cycles avgt 1132753.204 #/op
newArray:instructions avgt 3159465.006 #/op
Benchmark Mode Cnt Score Error Units
newArray2 avgt 4 125.531 ± 50.749 us/op
newArray2:CPI avgt 0.532 #/op
newArray2:L1-dcache-load-misses avgt 10345.720 #/op
newArray2:L1-dcache-loads avgt 85185.726 #/op
newArray2:L1-dcache-stores avgt 103096.223 #/op
newArray2:cycles avgt 346651.432 #/op
newArray2:instructions avgt 652155.439 #/op
Примечание различие в количестве циклов и инструкций.
<час>Среда:
Ubuntu 18.04.3 LTS
java version "12.0.2" 2019-07-16
Java(TM) SE Runtime Environment (build 12.0.2+10)
Java HotSpot(TM) 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)