java - एक एकल 2D सरणी का आवंटन एक लूप से अधिक क्यों लेता है जो एक ही कुल आकार और आकार के कई 1D सरणियों को आवंटित करता है?




performance (2)

जावा में बहुआयामी सरणियों को आवंटित करने के लिए एक अलग बाइटकोड निर्देश है - multianewarray

  • newArray बेंचमार्क newArray का उपयोग करता है;
  • newArray2 लूप में साधारण newarray को आमंत्रित करता है।

समस्या यह है कि हॉटस्पॉट JVM में multianewarray लिए कोई तेज़ पथ * नहीं है। इस निर्देश को हमेशा VM रनटाइम में निष्पादित किया जाता है। इसलिए, संकलित कोड में आवंटन इनलाइन नहीं है।

पहले बेंचमार्क में जावा और वीएम रनटाइम संदर्भों के बीच स्विचिंग के प्रदर्शन का जुर्माना देना पड़ता है। इसके अलावा, वीएम रनटाइम में सामान्य आवंटन कोड (सी ++ में लिखा गया) जेआईटी-संकलित कोड में इनबिल्ट आवंटन के रूप में अनुकूलित नहीं है, सिर्फ इसलिए कि यह सामान्य है , अर्थात विशेष ऑब्जेक्ट प्रकार या विशेष कॉल साइट के लिए अनुकूलित नहीं है, यह अतिरिक्त रनटाइम चेक आदि करता है।

यहाँ दोनों बेंचमार्क को async-profiler साथ देखने के परिणाम हैं। मैंने JDK 11.0.4 का उपयोग किया था, लेकिन JDK 8 के लिए चित्र समान दिखता है।

पहले मामले में, 99% समय OptoRuntime::multianewarray2_C - VM रनटाइम में C ++ कोड के अंदर बिताया जाता है।

दूसरे मामले में, अधिकांश ग्राफ हरा है, जिसका अर्थ है कि कार्यक्रम ज्यादातर जावा संदर्भ में चलता है, वास्तव में दिए गए बेंचमार्क के लिए अनुकूलित जेआईटी-संकलित कोड को निष्पादित करता है।

संपादित करें

* बस स्पष्ट करने के लिए: हॉटस्पॉट में multianewarray डिजाइन द्वारा बहुत अच्छी तरह से अनुकूलित नहीं है। जेआईटी संकलक दोनों में इस तरह के एक जटिल ऑपरेशन को ठीक से लागू करना महंगा पड़ता है, जबकि इस तरह के अनुकूलन के लाभ संदिग्ध होंगे: बहुप्रतिस्पर्धी सरणियों का आवंटन शायद ही कभी एक विशिष्ट अनुप्रयोग में एक प्रदर्शन अड़चन है।

मुझे लगा कि सीधे निर्माण करना जल्दी होगा, लेकिन वास्तव में, लूप जोड़ने में केवल आधा समय लगता है। ऐसा क्या हुआ जो इतना धीमा हो गया?

यहाँ परीक्षण कोड है

@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

परीक्षण का वातावरण इस प्रकार है

जेएमएच संस्करण: 1.21

VM संस्करण: JDK 1.8.0_212, OpenJDK 64-बिट सर्वर VM, 25.212-b04


multianewarray निर्देश के तहत ओरेकल डॉक्स में एक नोट कहता है:

एकल आयाम की एक सरणी बनाते समय newarray या anewarray ( §newarray , §anewarray ) का उपयोग करना अधिक कुशल हो सकता है।

आगे की:

newArray बेंचमार्क anewarray bytecode निर्देश का उपयोग करता है।

newArray2 बेंचमार्क multianewarray अनुदेश का उपयोग करता है।

और इससे ही फर्क पड़ता है। आइए perf लिनक्स प्रोफाइलर के उपयोग से प्राप्त आँकड़ों को देखें।

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

जबकि OptoRuntime::new_array_C , OptoRuntime::new_array_C newArray2 का उपयोग करता है, स्मृति को सरणियों के लिए आवंटित करने में बहुत कम समय खर्च करता है।

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 - कोड क्षेत्र जो सबसे अधिक निष्पादन समय लेते हैं।





performance