java - 단일 2D 배열을 할당하는 것이 동일한 총 크기와 모양의 여러 1D 배열을 할당하는 루프보다 시간이 더 걸리는 이유는 무엇입니까?
performance (2)
직접 작성하는 것이 더 빠를 것이라고 생각했지만 실제로 루프를 추가하는 데 절반의 시간이 걸립니다. 너무 느려진 일은 무엇입니까?
테스트 코드는 다음과 같습니다
@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
VM 버전 : JDK 1.8.0_212, OpenJDK 64 비트 서버 VM, 25.212-b04
Java에는 다차원 배열 (
multianewarray
을 할당하기위한 별도의 바이트 코드 명령어가 있습니다.
-
newArray
벤치 마크는multianewarray
바이트 코드를 사용합니다. -
newArray2
는 루프에서 간단한newarray
를 호출합니다.
문제는 HotSpot JVM에
multianewarray
바이트 코드에 대한
빠른 경로가 없다는 것
입니다.
이 명령어는 항상 VM 런타임에서 실행됩니다.
따라서 할당은 컴파일 된 코드에 인라인되지 않습니다.
첫 번째 벤치 마크는 Java와 VM 런타임 컨텍스트 간 전환에 따른 성능 저하를 초래해야합니다. 또한 VM 런타임의 공통 할당 코드 (C ++로 작성)는 JIT 컴파일 된 코드의 인라인 할당만큼 최적화되지 않습니다. 즉, 특정 객체 유형이나 특정 호출 사이트에 대해 최적화되지 않았기 때문입니다. 추가 런타임 검사 등을 수행합니다.
다음은 async-profiler 하여 두 벤치 마크를 프로파일 링 한 결과입니다. JDK 11.0.4를 사용했지만 JDK 8의 경우 그림이 비슷해 보입니다.
첫 번째 경우, 99 %의 시간이
OptoRuntime::multianewarray2_C
-VM 런타임의 C ++ 코드에서 소비됩니다.
두 번째 경우, 대부분의 그래프는 녹색입니다. 즉, 프로그램은 대부분 Java 컨텍스트에서 실행되므로 실제로 주어진 벤치 마크에 맞게 최적화 된 JIT 컴파일 된 코드를 실행합니다.
편집하다
*
명확히하기 위해 : HotSpot에서
multianewarray
는 디자인에 의해 잘 최적화되지 않았습니다.
두 JIT 컴파일러 모두에서 이러한 복잡한 작업을 올바르게 구현하는 데 비용이 많이 드는 반면, 이러한 최적화의 이점은 의문의 여지가 있습니다. 다차원 배열의 할당은 일반적인 응용 프로그램에서 성능 병목 현상이 거의 없습니다.
multianewarray
지침에 따라
Oracle Docs
의 메모에 다음과 같이 표시됩니다.
단일 차원의 배열을 만들 때
newarray
또는anewarray
( §newarray , §anewarray )를 사용하는 것이 더 효율적일 수 있습니다.
더욱이:
newArray
벤치 마크는
anewarray
바이트 코드 명령어를 사용합니다.
newArray2
벤치 마크는
multianewarray
바이트 코드 명령어를 사용합니다.
그리고 그것이 차이를 만드는 것입니다.
perf
Linux 프로파일 러를 사용하여 얻은 통계를 보자.
newArray
벤치 마크의 경우 생성 된 코드에서 가장 인기있는 영역
1
은 다음과 같습니다.
....[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 [email protected]
0.17% libjvm.so [email protected]
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...>
그리고
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...>
보다시피 느리게
newArray
벤치 마크를 위해 대부분의 시간이 소요됩니다.
22.58% MemAllocator::allocate
14.80% ObjArrayAllocator::initialize
12.92% TypeArrayKlass::multi_allocate
7.38% ObjArrayKlass::multi_allocate
newArray2
는
OptoRuntime::new_array_C
사용하지만 배열에 메모리를 할당하는 데 훨씬 적은 시간을 소비합니다.
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
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
사이클 수와 지침의 차이에 유의하십시오.
1-실행 시간이 가장 많이 소요되는 코드 영역