क्या सरणियों की नकल के लिए लूप की तुलना में System.arraycopy (…) का उपयोग करना बेहतर है?


89

मैं दो छोटी सरणियों को एक साथ रखने वाली वस्तुओं की एक नई सरणी बनाना चाहता हूं।

वे शून्य नहीं हो सकते, लेकिन आकार 0 हो सकते हैं।

मैं इन दो तरीकों के बीच नहीं चुना जा सकता है: क्या वे समकक्ष हैं या एक और अधिक कुशल है (उदाहरण के लिए system.arraycopy () प्रतियां पूरे विखंडू)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

या

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

क्या केवल अंतर कोड का रूप है?

संपादित करें: जुड़े हुए प्रश्न के लिए धन्यवाद, लेकिन वे एक अनसुलझी चर्चा है:

क्या यह वास्तव में तेज़ है अगर it is not for native types: बाइट [], वस्तु [], चार []? अन्य सभी मामलों में, एक प्रकार की जांच निष्पादित की जाती है, जो मेरा मामला होगा और इसलिए समतुल्य होगा ... नहीं?

एक अन्य जुड़े हुए प्रश्न पर, वे कहते हैं कि the size matters a lot, आकार के लिए> 24 system.arraycopy () जीतता है, 10 से छोटे के लिए, लूप के लिए मैनुअल बेहतर है ...

अब मैं वास्तव में भ्रमित हूँ।


16
arraycopy()एक देशी कॉल है, जो निश्चित रूप से सबसे तेज है।
सोथिरियोस डेलिमोलिसिस

4
क्या आपने दो अलग-अलग कार्यान्वयनों को बेंचमार्क करने की कोशिश की है?
एलेक्स


15
भविष्य में बनाए रखने के लिए आपको जो भी सबसे अधिक पठनीय और आसान लगता है उसे चुनना चाहिए। केवल जब आपने यह निर्धारित किया है कि यह एक अड़चन का स्रोत है, तो आपको अपना दृष्टिकोण बदलना चाहिए।
अर्धजी

1
पहिया को मजबूत मत करो!
camickr

जवाबों:


87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

मुझे पता है कि JUnit परीक्षण वास्तव में बेंचमार्किंग के लिए सर्वश्रेष्ठ नहीं हैं, लेकिन
testHardCopyBytes को पूरा करने के लिए 0.157s लिया
और
TestArrayCopyBytes को पूर्ण करने के लिए 0.086s लिया।

मुझे लगता है कि यह वर्चुअल मशीन पर निर्भर करता है, लेकिन ऐसा लगता है कि यह एकल सरणी तत्वों को कॉपी करने के बजाय मेमोरी के ब्लॉक कॉपी करता है। यह बिल्कुल प्रदर्शन को बढ़ाएगा।

संपादित करें:
ऐसा लगता है कि System.arraycopy का प्रदर्शन सभी जगह है। जब बाइट्स के बजाय स्ट्रिंग्स का उपयोग किया जाता है, और ऐरे छोटे होते हैं (आकार 10), मुझे ये परिणाम मिलते हैं:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

यहां बताया गया है कि जब एरेक्स 0x1000000 के आकार का होता है तो यह कैसा दिखता है। ऐसा लगता है कि System.arraycopy निश्चित रूप से बड़े सरणियों के साथ जीतता है।

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

कितनी अजीब बात है!

धन्यवाद, डैरन, यह इंगित करने के लिए कि संदर्भ अलग तरीके से कॉपी होते हैं। इसने इसे और अधिक रोचक समस्या बना दिया!


2
आपके प्रयास के लिए धन्यवाद, लेकिन आप प्रतीत होता है कि महत्वपूर्ण बिंदुओं से चूक गए: गैर-देशी प्रकार (किसी भी वर्ग के साथ एक यादृच्छिक वर्ग बनाएं ताकि सरणी में संदर्भ शामिल हों) और आकार ... छोटे सरणी आकार के लिए लगता है, मैनुअल फॉर-लूप तेज है। इसे ठीक करने के लिए देखभाल?
डैरन

2
अरे वाह, आप सही कह रहे हैं! यह रुचि है। बाइट्स के बजाय उन सरणियों में स्ट्रिंग्स रखने से बहुत बड़ा फर्क पड़ता है: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
ट्रेंट स्माल

तब आपको क्या परिणाम मिल रहे हैं? दिलचस्प भी सरणी आकार = 10 के साथ प्रयास करने के लिए होगा ... धन्यवाद! (मुझे लगता है कि मैं अपनी आईडीई यहाँ था, मैं संकलक के बिना कोडिंग हूँ)।
डैरन

खैर अब मैंने उन्हें कुछ System.nanoTime () कॉल और सेट आकार = 10 में लपेट लिया है, यह देखने के लिए कि प्रत्येक कितने नैनोसेकंड लेता है। लगता है कि छोटे आदिम सरणियों के लिए, लूप बेहतर हैं; संदर्भ के लिए, arrayCopy बेहतर है .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10290 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
ट्रेंट छोटा

बहुत ही रोचक परिणाम! आपको बहुत - बहुत धन्यवाद! क्या आप इसे शामिल करने के लिए अपने उत्तर को संपादित कर सकते हैं, और मैं ख़ुशी से इसे स्वीकार करूंगा ताकि यह सब देखने के लिए सबसे पहले रहे। आपको पहले ही अपना वोट मिल गया है। :)
डैरन

36

Arrays.copyOf(T[], int)पढ़ना आसान है। इंटर्नल इसका उपयोग करता है System.arraycopy()जो एक देशी कॉल है।

आप इसे तेजी से प्राप्त नहीं कर सकते हैं!


ऐसा लगता है कि आप काफी कुछ चीजों पर निर्भर हो सकते हैं, लेकिन उस फ़्यूचियन को इंगित करने के लिए धन्यवाद जो मुझे नहीं पता था और वास्तव में पढ़ने में आसान है। :)
डैरन

हाँ! यह काफी कुछ बातों पर निर्भर करता है जैसा @Svetoslav Tsolov ने कहा। मैं सिर्फ Arrays.copyOf को इंगित करना चाहता था
फिलिप सैंडर

2
copyOfहमेशा प्रतिस्थापित नहीं कर सकता arraycopy, लेकिन यह इस उपयोग के मामले के लिए उपयुक्त है।
Blaisorblade

1
नायब यदि आप प्रदर्शन देख रहे हैं तो यह अभिप्राय System.arraycopy () के रूप में जल्दी होगा क्योंकि इसमें मेमोरी आवंटन की आवश्यकता है। यदि यह एक लूप में है तो बार-बार आवंटन से कचरा संग्रह हो जाएगा जो एक बड़े पैमाने पर प्रदर्शन हिट होगा।
विल काल्डरवुड

1
@PhilippSander बस यह जांचने के लिए कि मैं बेवकूफ नहीं बन रहा हूं मैंने अपने गेम लूप में 1MB सरणी की प्रतिलिपि बनाने के लिए कोड जोड़ा, जो लगभग कभी भी जीसी को फायर नहीं करता है। Array.copyOf () के साथ मेरा DVM GC प्रति सेकंड 5 बार कॉल कर रहा था और गेम बेहद सुस्त हो गया था। मुझे लगता है कि यह कहना सुरक्षित है कि मेमोरी एलोकेशन हो रहा है।
विल काल्डरवुड

17

यह वर्चुअल मशीन पर निर्भर करता है, लेकिन System.arraycopy को आपको निकटतम प्रदर्शन देना चाहिए जो आपको मूल प्रदर्शन के लिए मिल सकता है।

मैंने एम्बेडेड सिस्टम के लिए जावा डेवलपर के रूप में 2 साल तक काम किया है (जहां प्रदर्शन एक बड़ी प्राथमिकता है) और हर जगह System.arraycopy का उपयोग किया जा सकता है, मैंने ज्यादातर इसका इस्तेमाल किया है / इसे मौजूदा कोड में उपयोग किया है। यह हमेशा लूप पर पसंद किया जाता है जब प्रदर्शन एक मुद्दा होता है। यदि प्रदर्शन कोई बड़ा मुद्दा नहीं है, तो मैं लूप के साथ जाऊंगा, हालांकि। पढ़ने में बहुत आसान।


सरणी आकार और प्रकार (मूल बनाम विरासत में मिली) जैसी चीजें पूर्वरूपता को प्रभावित करती हैं।
डैरन

2
हाँ, यह 'मूल प्रदर्शन' नहीं है, यही वजह है कि मैंने कहा कि मैं ज्यादातर इसका इस्तेमाल करता हूं, जहां मैं कर सकता हूं (आप देखेंगे कि यह ज्यादातर लूप कॉपी पर जीतता है)। मुझे लगता है कि इसका कारण यह है: जब यह एक आदिम प्रकार का छोटा सरणी है, तो 'कॉस्ट-टू-कॉल' प्रदर्शन को बढ़ावा देने से बड़ा है। जेएनआई का उपयोग एक ही कारण के लिए प्रदर्शन को नीचा कर सकता है - मूल कोड स्वयं तेज है, लेकिन इसे एक जावा प्रक्रिया से कॉल करना - इतना नहीं।

मामूली सुधार, Arrays.copy जेएनआई नहीं है, यह एक आंतरिक है। जेएनआई की तुलना में आंतरिक रूप से बहुत अधिक तेज है। कैसे और कब JIT संकलक इसे एक आंतरिक में बदल देता है, इसका उपयोग JVM / संकलक पर निर्भर करता है।
निट्सन वकार्ट

1
Arrays.copyमौजूद नहीं है, Arrays.copyOfएक पुस्तकालय समारोह है।
Blaisorblade

12

अटकलें और संभवतः पुरानी जानकारी पर भरोसा करने के बजाय, मैंने कुछ बेंचमार्क का उपयोग करके भाग लिया । वास्तव में, कैलीपर कुछ उदाहरणों के साथ आता है, जिसमें CopyArrayBenchmarkइस प्रश्न का सटीक माप शामिल है! आपको बस इतना चलाना है

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

मेरे परिणाम ओरेकल के जावा हॉटस्पॉट (टीएम) 64-बिट सर्वर वीएम, 1.8.0_31-बी 13 पर आधारित हैं, जो 2010 के मध्य में मैकबुक प्रो पर चल रहा है (इंटेल एरंडेल आई 7, 8 जीईबी रैम के साथ मैकओएस 10.11.6)। मुझे विश्वास नहीं है कि कच्चे समय के डेटा को पोस्ट करना उपयोगी है। बल्कि, मैं सहायक विज़ुअलाइज़ेशन के साथ निष्कर्ष निकालता हूँ।

संक्षेप में:

  • forप्रत्येक तत्व को एक नए तात्कालिक सरणी में कॉपी करने के लिए एक मैनुअल लूप लिखना कभी भी फायदेमंद नहीं होता है, चाहे वह लघु सरणियों या लंबी सरणियों के लिए हो।
  • Arrays.copyOf(array, array.length)और array.clone()दोनों लगातार तेज हैं। ये दोनों तकनीक प्रदर्शन में लगभग समान हैं; जो आप चुनते हैं वह स्वाद का मामला है।
  • System.arraycopy(src, 0, dest, 0, src.length)के रूप में लगभग के रूप में तेजी से होता है और नहीं, बल्कि काफी लगातार तो। (मामला 50000 सेकेंड के लिए देखें ।) उस वजह से, और कॉल की वाचालता, मैं आपको सलाह दूंगा कि क्या आपको ठीक नियंत्रण की आवश्यकता है कि किन तत्वों की नकल की जाती है।Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

यहाँ समय भूखंड हैं:

लंबाई 5 के सरणियों की नकल के लिए समय लंबाई 500 की सरणियों की नकल के लिए समय 50000 की लंबाई के सरणियों की नकल के लिए समय


3
क्या इंट कॉपी करने के बारे में कुछ अजीब है? यह अजीब लगता है कि यह बड़े पैमाने पर चींटियों पर धीमी गति से होता है।
व्हेलबर्ग

6

देशी तरीकों को निष्पादित करने की तरह Arrays.copyOf(T[], int)कुछ ओवरहेड होता है, लेकिन इसका मतलब यह नहीं है कि यह तेज़ नहीं है क्योंकि आप इसे जेएनआई का उपयोग करके निष्पादित कर रहे हैं।

सबसे आसान तरीका बेंचमार्क और टेस्ट लिखना है।

आप देख सकते हैं कि Arrays.copyOf(T[], int)आपके सामान्य forलूप से तेज है ।

यहाँ से बेंचमार्क कोड : -

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()किसी सरणी (या इसके कुछ हिस्सों) की प्रतिलिपि बनाने के लिए जेएनआई (जावा नेटिव इंटरफ़ेस) का उपयोग करता है, इसलिए यह बहुत तेज़ी से है, जैसा कि आप यहाँ पुष्टि कर सकते हैं


यह कोड int का उपयोग करता है [] क्या आप इसे स्ट्रिंग के साथ आज़मा सकते हैं [] (विभिन्न मूल्यों के साथ आरंभिक: "1", "2", आदि जब से वे अपरिवर्तनीय हैं
डैरन

1
जेएनआई बहुत धीमा हैSystem.arraycopyइसका उपयोग नहीं करता है।
चाई टी। रेक्स

नहीं, System.arraycopyजेएनआई का उपयोग नहीं करता है, जो केवल तीसरे पक्ष के पुस्तकालयों को कॉल करने के लिए है। इसके बजाय यह एक देशी कॉल है, जिसका अर्थ है कि इसके लिए वीएम में एक देशी कार्यान्वयन है।
१२:०१ पर spenenik

6

यह संभव नहीं है कि यह के कार्यान्वयन की Arrays.copyOfतुलना में तेज है : System.arraycopycopyOf

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

4

System.arraycopy()एक देशी कॉल है जो मेमोरी में सीधे कॉपी ऑपरेशन करता है। लूप के लिए सिंगल मैमोरी कॉपी हमेशा आपकी तुलना में तेज होगी


3
मैंने पढ़ा है कि गैर देशी प्रकारों (किसी भी निर्मित वर्ग, जैसे मेरी) के लिए यह इतना कुशल नहीं हो सकता है ... और छोटे आकार के आकार (मेरा मामला) के लिए मैनुअल लूप के लिए बेहतर हो सकता है ... टिप्पणी करने के लिए परवाह है?
डैरन

दरअसल, System.arraycopy () में कुछ ओवरहेड होते हैं इसलिए छोटे सरणियों के लिए (n = ~ 10) एक लूप वास्तव में तेज होता है
RecursiveExceptionException
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.