단일 2D 배열을 할당하는 것이 전체 크기와 모양이 동일한 여러 1D 배열을 할당하는 루프보다 더 오래 걸리는 이유는 무엇입니까?
직접 만드는 것이 더 빠르다고 생각했지만 실제로 루프를 추가하는 데 걸리는 시간은 절반에 불과합니다. 너무 느려진 무슨 일이 있었나요?
다음은 테스트 코드입니다.
@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의 경우 그림이 비슷해 보입니다.
첫 번째 경우 OptoRuntime::multianewarray2_C
에는 VM 런타임의 C ++ 코드에서 99 %의 시간이 소요 됩니다.
두 번째 경우 그래프의 대부분은 녹색입니다. 즉, 프로그램이 대부분 Java 컨텍스트에서 실행되고 실제로 주어진 벤치 마크에 최적화 된 JIT 컴파일 된 코드를 실행합니다.
편집하다
* 명확히하기 위해 : HotSpot multianewarray
에서는 설계 상 잘 최적화되지 않았습니다. 두 JIT 컴파일러 모두에서 이러한 복잡한 작업을 올바르게 구현하는 것은 비용이 많이 들지만 이러한 최적화의 이점은 의심 스러울 것입니다. 다차원 배열 할당은 일반적인 응용 프로그램에서 성능 병목 현상이 거의 발생하지 않습니다.
에서 메모 오라클 문서 세 이하 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)
'developer tip' 카테고리의 다른 글
FileWriter와 BufferedWriter의 Java 차이점 (0) | 2020.11.21 |
---|---|
Node.js 동일한 읽기 가능한 스트림을 여러 (쓰기 가능한) 대상으로 파이핑 (0) | 2020.11.21 |
모든 EntityManager를 close ()해야합니까? (0) | 2020.11.21 |
CSS, 중앙 div, 크기에 맞게 축소? (0) | 2020.11.21 |
SQL Server의 LDF 파일은 무엇입니까? (0) | 2020.11.21 |