2014-06-18 4 views
5

मैं बहुत धीमी DataInputStream.readByte() विधि के साथ समस्या का परीक्षण कर रहा हूं, और दिलचस्प, लेकिन समझ में आया समस्या मिली। मैं jdk1.7.0_40, Windows 7 64 bit का उपयोग कर रहा हूं।अजीब विधि आमंत्रण अनुकूलन समस्या

मान लें कि हमारे पास कुछ विशाल बाइट-सरणी है और इससे डेटा पढ़ रहा है। और चलो इस सरणी से बाइट-दर-बाइट पढ़ने के लिए 4 तरीकों की तुलना करते हैं: ->DataInputStream

  • ByteArrayInputStream के माध्यम से पढ़ने -

    1. सरल पाश
    2. ByteArrayInputStream के माध्यम से पढ़ने के माध्यम से पढ़ने> हमारे अपने DataInputStream कार्यान्वयन (MyDataInputStream)
    3. ByteArrayInputStream के माध्यम से पढ़ना और DataInputStream से विधि readByte() की प्रतिलिपि बनाएँ।

    मैं निम्नलिखित परिणाम (परीक्षण लूप पुनरावृत्ति के बाद लंबे समय तक) पाया है:

    • लूप aprox ले लिया। 312446094 एनएस
    • DataInputStream अपॉक्स लिया। 2555898090 एनएस
    • MyDataInputStream aprox लिया। "मूल" कार्यान्वयन के माध्यम से एक ही आपरेशन वस्तु विधि मंगलाचरण के माध्यम से 10 बार लंबे समय तक काम में लेते हैं, तो: २६३०६६४२९८ एनएस
    • विधि के माध्यम से readByte() प्रतिलिपि 309,265,568 एनएस

    दूसरे शब्दों में, हम अजीब अनुकूलन मुद्दा है ले लिया ।

    प्रश्न: क्यों?

    जानकारी के लिए:

    @Test 
    public void testBytes1() throws IOException { 
        byte[] bytes = new byte[1_000_000_000]; 
        Random r = new Random(); 
        for (int i = 0; i < bytes.length; i++) 
         bytes[i] = (byte) r.nextInt(); 
    
        do { 
         System.out.println(); 
    
         bytes[r.nextInt(1_000_000_000)] = (byte) r.nextInt(); 
    
         testLoop(bytes); 
         testDis(bytes); 
         testMyDis(bytes); 
         testViaMethod(bytes); 
        } while (true); 
    } 
    
    private void testDis(byte[] bytes) throws IOException { 
        long time1 = System.nanoTime(); 
        long c = 0; 
        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 
         DataInputStream dis = new DataInputStream(bais)) { 
         for (int i = 0; i < bytes.length; i++) { 
          c += dis.readByte(); 
         } 
        } 
        long time2 = System.nanoTime(); 
        System.out.println("Dis: \t\t\t\t" + (time2 - time1) + "\t\t\t\t" + c); 
    } 
    
    private void testMyDis(byte[] bytes) throws IOException { 
        long time1 = System.nanoTime(); 
        long c = 0; 
        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 
         MyDataInputStream dis = new MyDataInputStream(bais)) { 
         for (int i = 0; i < bytes.length; i++) { 
          c += dis.readByte(); 
         } 
        } 
        long time2 = System.nanoTime(); 
        System.out.println("My Dis: \t\t\t" + (time2 - time1) + "\t\t\t\t" + c); 
    } 
    
    private void testViaMethod(byte[] bytes) throws IOException { 
        long time1 = System.nanoTime(); 
        long c = 0; 
        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes) 
        ) { 
         for (int i = 0; i < bytes.length; i++) { 
          c += readByte(bais); 
         } 
        } 
        long time2 = System.nanoTime(); 
        System.out.println("Via method: \t\t" + (time2 - time1) + "\t\t\t\t" + c); 
    } 
    
    private void testLoop(byte[] bytes) { 
        long time1 = System.nanoTime(); 
        long c = 0; 
        for (int i = 0; i < bytes.length; i++) { 
         c += bytes[i]; 
        } 
        long time2 = System.nanoTime(); 
        System.out.println("Loop: \t\t\t\t" + (time2 - time1) + "\t\t\t\t" + c); 
    } 
    
    public final byte readByte(InputStream in) throws IOException { 
        int ch = in.read(); 
        if (ch < 0) 
         throw new EOFException(); 
        return (byte)(ch); 
    } 
    
    static class MyDataInputStream implements Closeable { 
    
        InputStream in; 
    
        MyDataInputStream(InputStream in) { 
         this.in = in; 
        } 
    
        public final byte readByte() throws IOException { 
         int ch = in.read(); 
         if (ch < 0) 
          throw new EOFException(); 
         return (byte)(ch); 
        } 
    
        @Override 
        public void close() throws IOException { 
         in.close(); 
        } 
    } 
    

    पी.एस. thoose, जो मेरे परिणामों के बारे में संदेह में है के लिए अद्यतन, इस प्रिंटआउट है, -XX:+PrintCompilation -verbose:gc -XX:CICompilerCount=1

     37 1    java.lang.String::hashCode (55 bytes) 
        41 2    java.lang.String::charAt (29 bytes) 
        43 3    java.lang.String::indexOf (70 bytes) 
        49 4    java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes) 
        52 5    java.lang.AbstractStringBuilder::append (29 bytes) 
        237 6    java.util.Random::nextInt (7 bytes) 
        237 9  n  sun.misc.Unsafe::compareAndSwapLong (native) 
        238 7    java.util.concurrent.atomic.AtomicLong::get (5 bytes) 
        238 8    java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes) 
        239 10    java.util.Random::next (47 bytes) 
        239 11 %   fias.TestArrays::testBytes1 @ 15 (77 bytes) 
        9645 11 %   fias.TestArrays::testBytes1 @ -2 (77 bytes) made not entrant 
    
        9646 12 %   fias.TestArrays::testLoop @ 10 (77 bytes) 
        9964 12 %   fias.TestArrays::testLoop @ -2 (77 bytes) made not entrant 
    Loop:    318726397    -500090432 
        9965 13    java.io.DataInputStream::readByte (23 bytes) 
        9966 14 s   java.io.ByteArrayInputStream::read (36 bytes) 
        9967 15 % !   fias.TestArrays::testDis @ 37 (279 bytes) 
    Dis:    2684374258    -500090432 
        12651 16    fias.TestArrays$MyDataInputStream::readByte (23 bytes) 
        12652 17 % !   fias.TestArrays::testMyDis @ 37 (279 bytes) 
    My Dis:    2675570541    -500090432 
        15327 18    fias.TestArrays::readByte (20 bytes) 
        15328 19 % !   fias.TestArrays::testViaMethod @ 23 (179 bytes) 
    Via method:   2367507141    -500090432 
    
        17694 20    fias.TestArrays::testLoop (77 bytes) 
        17699 21 %   fias.TestArrays::testLoop @ 10 (77 bytes) 
    Loop:    374525891    -500090567 
        18069 22 !   fias.TestArrays::testDis (279 bytes) 
    Dis:    2674626125    -500090567 
        20745 23 !   fias.TestArrays::testMyDis (279 bytes) 
    My Dis:    2671418683    -500090567 
        23417 24 !   fias.TestArrays::testViaMethod (179 bytes) 
    Via method:   2359181776    -500090567 
    
    Loop:    315081855    -500090663 
    Dis:    2558738649    -500090663 
    My Dis:    2627056034    -500090663 
    Via method:   311692727    -500090663 
    
    Loop:    317813286    -500090778 
    Dis:    2565161726    -500090778 
    My Dis:    2630665760    -500090778 
    Via method:   314594434    -500090778 
    
    Loop:    313695660    -500090797 
    Dis:    2568251556    -500090797 
    My Dis:    2635236578    -500090797 
    Via method:   311882312    -500090797 
    
    Loop:    316781686    -500090929 
    Dis:    2563535623    -500090929 
    My Dis:    2638487613    -500090929 
    Via method:   313170789    -500090929 
    

    युपीडी -2 का उपयोग कर: यहाँ benchmark और results कृपया @maaartinus द्वारा दिए गए है।

  • +2

    आप कहते हैं: "लूप aprox ले लिया। 312446094 एनएस "। यह असली परिणाम इस के रूप में सटीक नहीं हो सकता है, सिर्फ इसलिए कि आप वास्तव में इस अवधि के दौरान इस थ्रेड पर 50% ?, 100%?) के दौरान कितना समय चल रहे हैं और यह एक बदल सकता है बहुत सी चीज। मैं आपको इस प्रोग्राम को कई बार चलाने, स्टोर करने के समय, और एक विशिष्ट निष्पादन समय के बजाय औसत मूल्य का उपयोग करने की सलाह देता हूं। – Gwenc37

    +3

    आपका बेंचमार्क उचित गर्म करने की अनुमति नहीं देता है + आपके पास परीक्षण ढांचे का ओवरहेड है जो पूर्वाग्रह हो सकता है परिणाम। आपको [उचित बेंचमार्किंग टूल] (http://stackoverflow.com/a/15787478/829571) का उपयोग करना चाहिए। – assylias

    +0

    थीस के परिणाम कई टेन्स परीक्षण लूप पुनरावृत्तियों के बाद लिया जाता है। यदि आप अधिक सटीक परीक्षण की सलाह दे सकते हैं - कृपया , सुझाव दें। – Andremoniy

    उत्तर

    -1

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

    इस मामले में, testloop के लिए, हम स्मृति मेमोरी लागत ~ 3 एनएस (पूर्णांक ऑपरेशंस, जैसे i ++, c +) दूसरों के लिए अनुमान लगा सकते हैं, फ़ंक्शन इनवोकेशन की 2 एडिटोनल परतें हैं। इसलिए प्रत्येक फ़ंक्शन कॉल ~ 15 एनएस वास्तविकता हम कह सकते हैं कि फंक्शन कॉल बहुत तेज है।

    एकमात्र बिंदु यह है कि प्रत्येक प्रक्रिया में 2 000 000 000 फ़ंक्शन कॉल हैं, जो वास्तव में एक बड़ी संख्या है।किसी भी स्ट्रीम का प्रयोग नहीं करते, बस कॉल अतिरिक्त समारोह के साथ बाइट्स पढ़ें::

    समारोह नीचे जोड़ने के लिए,

    public final long getByte(long c, byte value, int dep) { 
        if (dep > 0) { 
         return getByte(c, value, dep - 1); 
        } 
        return c + value; 
    } 
    

    तो जैसे testLoop में आह्वान

    एक और परीक्षण का मामला समारोह कॉल की लागत साबित करने के लिए है:

    c = getByte(c, bytes[i], 2); 
    
    तो

    एक ही स्तर के लिए अंतिम मूल्य वृद्धि:

    लूप: 4044010718 -499870245

    जिले: 5182272442 -499870245

    मेरे जिले: 5228065271 -499870245

    विधि के माध्यम से: 655108198 -499870245

    +0

    आपके उत्तर में क्या गलत है: 1) दोनों मामलों में: 'dis.readByte()' और स्थानीय ऑब्जेक्ट 'readByte()' विधि कॉल - "फ़ंक्शन" कॉल के माध्यम से उपयोग किया जाता है, इसलिए यह अलग-अलग व्याख्या नहीं करता है। 2) भले ही कारण "फ़ंक्शन कॉल" से संबंधित है, यह स्पष्ट नहीं करता है कि क्यों JVM केवल स्थानीय विधि कॉल को अनुकूलित करता है। – Andremoniy

    +0

    @Andremoniy 'dis.readByte()' के मामले में अधिक फ़ंक्शन कॉल हैं। लेकिन आप सही हैं कि वे सभी को रेखांकित किया जा सकता है और वे वास्तव में करते हैं। ओटीओएच, एक लंबी विधि निकाय लूप अनोलिंग को रेखांकित या कम करने से रोक सकती है। उस ने कहा, यहां तक ​​कि अलेक्जेंडर के जवाब के साथ, यह अभी भी अस्पष्ट है कि क्या हो रहा है। – maaartinus

    3

    हैरानी की बात है, कारण पर कोशिश-साथ-संसाधनों बयान है MyDataInputStream/DataInputStream

    यदि हम कोशिश ब्लॉक ब्लॉक के अंदर प्रारंभिक स्थानांतरित करते हैं तो लूप/विधि आमंत्रण

    private void testMyDis(byte[] bytes) throws IOException { 
        final long time1 = System.nanoTime(); 
        long c = 0; 
        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 
         final MyDataInputStream dis = new MyDataInputStream(bais); 
         for (int i = 0; i < bytes.length; i++) { 
          c += dis.readByte(); 
         } 
        } 
        final long time2 = System.nanoTime(); 
        System.out.println("My Dis: \t\t\t" + (time2 - time1) + "\t\t\t\t" + c); 
    } 
    

    मुझे लगता है कि है कि अनावश्यक संसाधन JIT साथ उपयोग नहीं कर सकते Range Check Elimination

    +0

    यह कमाल है! मेरे लिए +1 है, लेकिन यह अभी भी 'DataInputStream' के बारे में स्पष्ट नहीं है: परीक्षण में 'प्रयास-संसाधनों' ब्लॉक को समाप्त करने से इसे तेज करने के लिए कोई शुल्क नहीं मिलता है। – Andremoniy

    +2

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

    संबंधित मुद्दे