2016-09-04 10 views
14

जबकि मैं प्रत्यक्ष java.nio.ByteBuffer के पढ़ने के प्रदर्शन का परीक्षण कर रहा था, मैंने देखा कि पूर्ण पढ़ने सापेक्ष पढ़ने से औसत 2x गुना तेज है। इसके अलावा यदि मैं रिश्तेदार बनाम पूर्ण पढ़ने के स्रोत कोड की तुलना करता हूं, तो कोड रिश्तेदार पढ़ता रहता है और आंतरिक काउंटर को छोड़कर कोड काफी समान होता है। मुझे आश्चर्य है कि मुझे गति में इतना अंतर क्यों दिखता है?डायरेक्ट बाइटबफर रिश्तेदार बनाम पूर्ण पढ़ने का प्रदर्शन

नीचे मेरी JMH बेंचमार्क के स्रोत कोड है:

public class DirectByteBufferReadBenchmark { 

    private static final int OBJ_SIZE = 8 + 4 + 1; 
    private static final int NUM_ELEM = 10_000_000; 

    @State(Scope.Benchmark) 
    public static class Data { 

     private ByteBuffer directByteBuffer; 

     @Setup 
     public void setup() { 
      directByteBuffer = ByteBuffer.allocateDirect(OBJ_SIZE * NUM_ELEM); 
      for (int i = 0; i < NUM_ELEM; i++) { 
       directByteBuffer.putLong(i); 
       directByteBuffer.putInt(i); 
       directByteBuffer.put((byte) (i & 1)); 
      } 
     } 
    } 



    @Benchmark 
    @BenchmarkMode(Mode.Throughput) 
    @OutputTimeUnit(TimeUnit.SECONDS) 
    public long testReadAbsolute(Data d) throws InterruptedException { 
     long val = 0l; 
     for (int i = 0; i < NUM_ELEM; i++) { 
      int index = OBJ_SIZE * i; 
      val += d.directByteBuffer.getLong(index); 
      d.directByteBuffer.getInt(index + 8); 
      d.directByteBuffer.get(index + 12); 
     } 
     return val; 
    } 

    @Benchmark 
    @BenchmarkMode(Mode.Throughput) 
    @OutputTimeUnit(TimeUnit.SECONDS) 
    public long testReadRelative(Data d) throws InterruptedException { 
     d.directByteBuffer.rewind(); 

     long val = 0l; 
     for (int i = 0; i < NUM_ELEM; i++) { 
      val += d.directByteBuffer.getLong(); 
      d.directByteBuffer.getInt(); 
      d.directByteBuffer.get(); 
     } 

     return val; 
    } 

    public static void main(String[] args) throws Exception { 
     Options opt = new OptionsBuilder() 
      .include(DirectByteBufferReadBenchmark.class.getSimpleName()) 
      .warmupIterations(5) 
      .measurementIterations(5) 
      .forks(3) 
      .threads(1) 
      .build(); 

     new Runner(opt).run(); 
    } 
} 

और ये मेरी बेंचमार्क रन के परिणाम हैं:

Benchmark          Mode Cnt Score Error Units 
DirectByteBufferReadBenchmark.testReadAbsolute thrpt 15 88.605 ± 9.276 ops/s 
DirectByteBufferReadBenchmark.testReadRelative thrpt 15 42.904 ± 3.018 ops/s 

परीक्षण एक macbookpro (2.2GHz इंटेल कोर पर चलाया गया था i7, 16 जीबी डीडीआर 3) और जेडीके 1.8.0_73।

अद्यतन

मैं JDK 9-ईए b134 के साथ ही परीक्षण चलाते हैं। दोनों परीक्षण एक ~ 10% गति वृद्धि दिखाते हैं लेकिन दोनों के बीच गति अंतर समान रहता है।

# JMH 1.13 (released 45 days ago) 
# VM version: JDK 9-ea, VM 9-ea+134 
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/bin/java 
# VM options: <none> 


Benchmark          Mode Cnt Score Error Units 
DirectByteBufferReadBenchmark.testReadAbsolute thrpt 15 102.170 ± 10.199 ops/s 
DirectByteBufferReadBenchmark.testReadRelative thrpt 15 45.988 ± 3.896 ops/s 

उत्तर

19

जेडीके 8 वास्तव में सापेक्ष बाइटबफर पहुंच के साथ लूप के लिए खराब कोड उत्पन्न करता है।

जेएमएच ने perfasm प्रोफाइलर बनाया है जो सबसे गर्म क्षेत्रों के लिए उत्पन्न असेंबली कोड प्रिंट करता है। मैं used it to compare है संकलित testReadAbsolute बनाम testReadRelative, और यहाँ हैं मुख्य अंतर:

  1. सापेक्ष getLong/getInt/ get अद्यतन ByteBuffer की स्थिति क्षेत्र। वीएम इन अद्यतनों को अनुकूलित नहीं करता है: प्रत्येक लूप पुनरावृत्ति पर 3 मेमोरी लिखती हैं।

  2. position रेंज चेक समाप्त नहीं किया गया है: प्रत्येक लूप पुनरावृत्ति पर सशर्त शाखा संकलित कोड में बनी हुई है।

  3. चूंकि अनावश्यक फ़ील्ड अपडेट और रेंज चेक लूप बॉडी को लंबे समय तक बनाते हैं, वीएम लूप के केवल 2 पुनरावृत्तियों को अनलोल करता है। पूर्ण पहुंच वाले लूप के लिए संकलित संस्करण में 16 पुनरावृत्तियों को अनियंत्रित किया गया है।

testReadAbsolute बहुत अच्छी तरह से संकलित किया गया है: मुख्य लूप सिर्फ 16 देशांतर पढ़ता है, उन्हें सार और अगर index < 10_000_000 - 16 अगले चरण के लिए कूदता है। directByteBuffer की स्थिति अपडेट नहीं की गई है। हालांकि, JVM testReadRelative के लिए स्मार्ट नहीं है: ऐसा लगता है कि यह किसी ऑब्जेक्ट के फील्ड एक्सेस को ऑप्टिमाइज़ नहीं कर सकता है।

बाइटबफर को अनुकूलित करने के लिए जेडीके 9 में बहुत अधिक काम था। मैंने जेडीके 9-एए बी 134 पर एक ही परीक्षण चलाया है, और सत्यापित किया है कि testReadRelative में अनावश्यक स्मृति लिखने और रेंज चेक नहीं हैं। अब यह लगभग testReadAbsolute जितना तेज़ चलता है।

// JDK 1.8.0_92, VM 25.92-b14 

Benchmark          Mode Cnt Score Error Units 
DirectByteBufferReadBenchmark.testReadAbsolute thrpt 10 99,727 ± 0,542 ops/s 
DirectByteBufferReadBenchmark.testReadRelative thrpt 10 47,126 ± 0,289 ops/s 

// JDK 9-ea, VM 9-ea+134 

Benchmark          Mode Cnt Score Error Units 
DirectByteBufferReadBenchmark.testReadAbsolute thrpt 10 109,369 ± 0,403 ops/s 
DirectByteBufferReadBenchmark.testReadRelative thrpt 10 97,140 ± 0,572 ops/s 

अद्यतन

आदेश अनुकूलन के साथ JIT कम्पाइलर मदद करने के लिए मैं दोनों मानक में स्थानीय चर

ByteBuffer directByteBuffer = d.directByteBuffer 

पेश किया है। अन्यथा संकेत का स्तर संकलक को ByteBuffer.position फ़ील्ड अपडेट को खत्म करने की अनुमति नहीं देता है।

+0

आपके उत्तर के लिए धन्यवाद। मैंने जेडीके 9 के साथ परीक्षण किया, सवाल में अपडेट देखें, हालांकि मुझे रिश्तेदार पढ़ने को बेहतर प्रदर्शन करने के लिए नहीं देखा जाता है। कोई विचार क्यों? –

+0

@VladimirG। हां, मेरा बेंचमार्क वास्तव में थोड़ा अलग था। मैंने जवाब अपडेट कर लिया है। कारण अभी भी वही हैं: जेआईटी 'स्थिति' फ़ील्ड के अपडेट को ऑप्टिमाइज़ नहीं करता है, यही कारण है कि रिश्तेदार बाइटबफर का उपयोग कम कुशल प्रतीत होता है। – apangin

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