2012-01-19 23 views
32

जावा में लिखा निम्नलिखित कार्यक्रम का निरीक्षण करें (runnable संस्करण इस प्रकार पूरा, लेकिन कार्यक्रम के महत्वपूर्ण हिस्सा आगे नीचे एक छोटा सा टुकड़ा में है):क्यों एक अस्थिर पढ़ने और फ़ील्ड सदस्य को लिखना जावा में स्केलेबल नहीं है?

import java.util.ArrayList; 



/** A not easy to explain benchmark. 
*/ 
class MultiVolatileJavaExperiment { 

    public static void main(String[] args) { 
     (new MultiVolatileJavaExperiment()).mainMethod(args); 
    } 

    int size = Integer.parseInt(System.getProperty("size")); 
    int par = Integer.parseInt(System.getProperty("par")); 

    public void mainMethod(String[] args) { 
     int times = 0; 
     if (args.length == 0) times = 1; 
     else times = Integer.parseInt(args[0]); 
     ArrayList <Long> measurements = new ArrayList <Long>(); 

     for (int i = 0; i < times; i++) { 
      long start = System.currentTimeMillis(); 
      run(); 
      long end = System.currentTimeMillis(); 

      long time = (end - start); 
      System.out.println(i + ") Running time: " + time + " ms"); 
      measurements.add(time); 
     } 

     System.out.println(">>>"); 
     System.out.println(">>> All running times: " + measurements); 
     System.out.println(">>>"); 
    } 

    public void run() { 
     int sz = size/par; 
     ArrayList <Thread> threads = new ArrayList <Thread>(); 

     for (int i = 0; i < par; i++) { 
      threads.add(new Reader(sz)); 
      threads.get(i).start(); 
     } 
     for (int i = 0; i < par; i++) { 
      try { 
       threads.get(i).join(); 
      } catch (Exception e) {} 
     } 
    } 

    final class Foo { 
     int x = 0; 
    } 

    final class Reader extends Thread { 
     volatile Foo vfoo = new Foo(); 
     Foo bar = null; 
     int sz; 

     public Reader(int _sz) { 
      sz = _sz; 
     } 

     public void run() { 
      int i = 0; 
      while (i < sz) { 
       vfoo.x = 1; 
       // with the following line commented 
       // the scalability is almost linear 
       bar = vfoo; // <- makes benchmark 2x slower for 2 processors - why? 
       i++; 
      } 
     } 
    } 

} 

स्पष्टीकरण: कार्यक्रम वास्तव में बहुत सरल है । यह सिस्टम गुणों से size और par पूर्णांक लोड करता है (-D ध्वज के साथ जेवीएम पास किया गया) - ये इनपुट लंबाई और बाद में उपयोग करने के लिए धागे की संख्या हैं। इसके बाद यह पहली कमांड लाइन तर्क का विश्लेषण करता है जो कहता है कि कार्यक्रम को दोहराने के लिए कितना समय है (हम यह सुनिश्चित करना चाहते हैं कि जेआईटी ने अपना काम किया है और अधिक विश्वसनीय माप हैं)।

प्रत्येक पुनरावृत्ति में run विधि को बुलाया जाता है। यह विधि बस par धागे शुरू करती है, जिनमें से प्रत्येक size/par पुनरावृत्तियों के साथ एक लूप करेगा। थ्रेड बॉडी को Reader कक्षा में परिभाषित किया गया है। लूप की प्रत्येक पुनरावृत्ति एक अस्थिर सदस्य vfoo पढ़ती है और 1 को अपने सार्वजनिक क्षेत्र में असाइन करती है। उसके बाद, vfoo एक बार फिर पढ़ा जाता है और गैर-अस्थिर फ़ील्ड bar को असाइन किया जाता है।

सूचना कैसे समय कार्यक्रम पाश शरीर को क्रियान्वित कर रहा है, तो सूत्र में run के सबसे इस बेंचमार्क का ध्यान केंद्रित है:

final class Reader extends Thread { 
     volatile Foo vfoo = new Foo(); 
     Foo bar = null; 
     int sz; 

     public Reader(int _sz) { 
      sz = _sz; 
     } 

     public void run() { 
      int i = 0; 
      while (i < sz) { 
       vfoo.x = 1; 
       // with the following line commented 
       // the scalability is almost linear 
       bar = vfoo; // <- makes benchmark 2x slower for 2 processors - why? 
       i++; 
      } 
     } 
    } 

टिप्पणियों: एक

Ubuntu Server 10.04.3 LTS 
8 core Intel(R) Xeon(R) CPU X5355 @2.66GHz 
~20GB ram 
java version "1.6.0_26" 
Java(TM) SE Runtime Environment (build 1.6.0_26-b03) 
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode) 
पर java -Xmx512m -Xms512m -server -Dsize=500000000 -Dpar=1 MultiVolatileJavaExperiment 10 चल रहा है

मुझे निम्नलिखित बार मिलते हैं:

>>> All running times: [821, 750, 1011, 750, 758, 755, 1219, 751, 751, 1012] 

अब, -Dpar=2 की स्थापना, मैं:

>>> All running times: [1618, 380, 1476, 1245, 1390, 1391, 1445, 1393, 1511, 1508] 

जाहिर है, यह किसी कारण से स्केल नहीं करता है - मैं हालांकि यह एक में होना प्रतीत होता है दूसरा उत्पादन दोगुनी गति से होने के लिए (उम्मीद है | प्रारंभिक पुनरावृत्तियों के - 380ms)।

दिलचस्प है, लाइन bar = vfoo (जो भी एक अस्थिर लिखने होना चाहिए नहीं है) बाहर टिप्पणी करते हुए निम्नलिखित समय 1,2,4,8 को -Dpar सेट के लिए अर्जित करता है।

>>> All running times: [762, 563, 563, 563, 563, 563, 570, 566, 563, 563] 
>>> All running times: [387, 287, 285, 284, 283, 281, 282, 282, 281, 282] 
>>> All running times: [204, 146, 143, 142, 141, 141, 141, 141, 141, 141] 
>>> All running times: [120, 78, 74, 74, 81, 75, 73, 73, 72, 71] 

यह पूरी तरह से स्केल करता है।

विश्लेषण: सबसे पहले, यहां कोई कचरा संग्रहण चक्र नहीं है (मैंने -verbose:gc भी इसे जांचने के लिए जोड़ा है)।

मुझे अपने आईमैक पर समान परिणाम मिलते हैं।

प्रत्येक धागा अपने स्वयं के क्षेत्र के लिए लिख रहा है, और अलग अलग Foo वस्तु अलग धागे से संबंधित उदाहरणों में एक ही cachelines में समाप्त हो प्रतीत नहीं होते हैं - Foo में और अधिक सदस्यों को जोड़ने इसके आकार को बढ़ाने के लिए माप नहीं बदलता है । एल 1 कैश लाइन को भरने के लिए प्रत्येक थ्रेड ऑब्जेक्ट इंस्टेंस में पर्याप्त फ़ील्ड से अधिक है। तो यह शायद एक स्मृति मुद्दा नहीं है।

मेरा अगला सोचा कि JIT, कुछ अजीब कर हो सकती है क्योंकि जल्दी पुनरावृत्तियों आमतौर पर uncommented संस्करण में अपेक्षा के अनुरूप पैमाने करना था, इसलिए मैं विधानसभा (this post on how to do that देखें) मुद्रण द्वारा इस की जाँच की।

java -Xmx512m -Xms512m -server -XX:CompileCommand=print,*Reader.run MultiVolatileJavaExperiment -Dsize=500000000 -Dpar=1 10 

और मैं Reader में Jitted विधि run के लिए 2 संस्करणों के लिए इन 2 आउटपुट मिलता है। टिप्पणी की (ठीक से स्केलेबल) संस्करण:

[Verified Entry Point] 
    0xf36c9fac: mov %eax,-0x3000(%esp) 
    0xf36c9fb3: push %ebp 
    0xf36c9fb4: sub $0x8,%esp 
    0xf36c9fba: mov 0x68(%ecx),%ebx 
    0xf36c9fbd: test %ebx,%ebx 
    0xf36c9fbf: jle 0xf36c9fec 
    0xf36c9fc1: xor %ebx,%ebx 
    0xf36c9fc3: nopw 0x0(%eax,%eax,1) 
    0xf36c9fcc: xchg %ax,%ax 
    0xf36c9fd0: mov 0x6c(%ecx),%ebp 
    0xf36c9fd3: test %ebp,%ebp 
    0xf36c9fd5: je  0xf36c9ff7 
    0xf36c9fd7: movl $0x1,0x8(%ebp) 

--------------------------------------------- 

    0xf36c9fde: mov 0x68(%ecx),%ebp 
    0xf36c9fe1: inc %ebx    ; OopMap{ecx=Oop off=66} 
             ;*goto 
             ; - org.scalapool.bench.MultiVolatileJavaExperiment$Reader::[email protected] (line 83) 

--------------------------------------------- 

    0xf36c9fe2: test %edi,0xf7725000 ; {poll} 
    0xf36c9fe8: cmp %ebp,%ebx 
    0xf36c9fea: jl  0xf36c9fd0 
    0xf36c9fec: add $0x8,%esp 
    0xf36c9fef: pop %ebp 
    0xf36c9ff0: test %eax,0xf7725000 ; {poll_return} 
    0xf36c9ff6: ret  
    0xf36c9ff7: mov $0xfffffff6,%ecx 
    0xf36c9ffc: xchg %ax,%ax 
    0xf36c9fff: call 0xf36a56a0   ; OopMap{off=100} 
             ;*putfield x 
             ; - org.scalapool.bench.MultiVolatileJavaExperiment$Reader::[email protected] (line 79) 
             ; {runtime_call} 
    0xf36ca004: call 0xf6f877a0   ; {runtime_call} 

uncommented bar = vfoo (गैर स्केलेबल, धीमी) संस्करण:

[Verified Entry Point] 
    0xf3771aac: mov %eax,-0x3000(%esp) 
    0xf3771ab3: push %ebp 
    0xf3771ab4: sub $0x8,%esp 
    0xf3771aba: mov 0x68(%ecx),%ebx 
    0xf3771abd: test %ebx,%ebx 
    0xf3771abf: jle 0xf3771afe 
    0xf3771ac1: xor %ebx,%ebx 
    0xf3771ac3: nopw 0x0(%eax,%eax,1) 
    0xf3771acc: xchg %ax,%ax 
    0xf3771ad0: mov 0x6c(%ecx),%ebp 
    0xf3771ad3: test %ebp,%ebp 
    0xf3771ad5: je  0xf3771b09 
    0xf3771ad7: movl $0x1,0x8(%ebp) 

------------------------------------------------- 

    0xf3771ade: mov 0x6c(%ecx),%ebp 
    0xf3771ae1: mov %ebp,0x70(%ecx) 
    0xf3771ae4: mov 0x68(%ecx),%edi 
    0xf3771ae7: inc %ebx 
    0xf3771ae8: mov %ecx,%eax 
    0xf3771aea: shr $0x9,%eax 
    0xf3771aed: movb $0x0,-0x3113c300(%eax) ; OopMap{ecx=Oop off=84} 
             ;*goto 
             ; - org.scalapool.bench.MultiVolatileJavaExperiment$Reader::[email protected] (line 83) 

----------------------------------------------- 

    0xf3771af4: test %edi,0xf77ce000 ; {poll} 
    0xf3771afa: cmp %edi,%ebx 
    0xf3771afc: jl  0xf3771ad0 
    0xf3771afe: add $0x8,%esp 
    0xf3771b01: pop %ebp 
    0xf3771b02: test %eax,0xf77ce000 ; {poll_return} 
    0xf3771b08: ret  
    0xf3771b09: mov $0xfffffff6,%ecx 
    0xf3771b0e: nop  
    0xf3771b0f: call 0xf374e6a0   ; OopMap{off=116} 
             ;*putfield x 
             ; - org.scalapool.bench.MultiVolatileJavaExperiment$Reader::[email protected] (line 79) 
             ; {runtime_call} 
    0xf3771b14: call 0xf70307a0   ; {runtime_call} 

दो संस्करणों में मतभेद --------- के भीतर हैं। मुझे असेंबली में सिंक्रनाइज़ेशन निर्देश मिलने की उम्मीद है जो प्रदर्शन के मुद्दे के लिए हो सकता है - जबकि कुछ अतिरिक्त shift, mov और inc निर्देश पूर्ण प्रदर्शन संख्याओं को प्रभावित कर सकते हैं, मुझे नहीं लगता कि वे स्केलेबिलिटी को कैसे प्रभावित कर सकते हैं।

तो, मुझे संदेह है कि यह वर्ग में किसी फ़ील्ड को संग्रहीत करने से संबंधित स्मृति प्रकार का कुछ प्रकार है। दूसरी तरफ, मैं यह भी मानने के इच्छुक हूं कि जेआईटी कुछ मजाकिया करता है, क्योंकि एक पुनरावृत्ति में मापा गया समय जितना तेज़ होगा, उतना ही होना चाहिए।

क्या कोई यहां समझा सकता है कि क्या हो रहा है? कृपया सटीक रहें और उन संदर्भों को शामिल करें जो आपके दावों का समर्थन करते हैं।

धन्यवाद!

संपादित करें:

यहाँ तेजी से (स्केलेबल) संस्करण के लिए बाईटकोड है:

public void run(); 
    LineNumberTable: 
    line 77: 0 
    line 78: 2 
    line 79: 10 
    line 83: 18 
    line 85: 24 



    Code: 
    Stack=2, Locals=2, Args_size=1 
    0: iconst_0 
    1: istore_1 
    2: iload_1 
    3: aload_0 
    4: getfield #7; //Field sz:I 
    7: if_icmpge 24 
    10: aload_0 
    11: getfield #5; //Field vfoo:Lorg/scalapool/bench/MultiVolatileJavaExperiment$Foo; 
    14: iconst_1 
    15: putfield #8; //Field org/scalapool/bench/MultiVolatileJavaExperiment$Foo.x:I 
    18: iinc 1, 1 
    21: goto 2 
    24: return 
    LineNumberTable: 
    line 77: 0 
    line 78: 2 
    line 79: 10 
    line 83: 18 
    line 85: 24 

    StackMapTable: number_of_entries = 2 
    frame_type = 252 /* append */ 
    offset_delta = 2 
    locals = [ int ] 
    frame_type = 21 /* same */ 

bar = vfoo साथ धीमी गति से (गैर स्केलेबल) संस्करण:

public void run(); 
    LineNumberTable: 
    line 77: 0 
    line 78: 2 
    line 79: 10 
    line 82: 18 
    line 83: 26 
    line 85: 32 



    Code: 
    Stack=2, Locals=2, Args_size=1 
    0: iconst_0 
    1: istore_1 
    2: iload_1 
    3: aload_0 
    4: getfield #7; //Field sz:I 
    7: if_icmpge 32 
    10: aload_0 
    11: getfield #5; //Field vfoo:Lorg/scalapool/bench/MultiVolatileJavaExperiment$Foo; 
    14: iconst_1 
    15: putfield #8; //Field org/scalapool/bench/MultiVolatileJavaExperiment$Foo.x:I 
    18: aload_0 
    19: aload_0 
    20: getfield #5; //Field vfoo:Lorg/scalapool/bench/MultiVolatileJavaExperiment$Foo; 
    23: putfield #6; //Field bar:Lorg/scalapool/bench/MultiVolatileJavaExperiment$Foo; 
    26: iinc 1, 1 
    29: goto 2 
    32: return 
    LineNumberTable: 
    line 77: 0 
    line 78: 2 
    line 79: 10 
    line 82: 18 
    line 83: 26 
    line 85: 32 

    StackMapTable: number_of_entries = 2 
    frame_type = 252 /* append */ 
    offset_delta = 2 
    locals = [ int ] 
    frame_type = 29 /* same */ 

अधिक मैं कर रहा हूँ इसके साथ प्रयोग करते हुए, मुझे ऐसा लगता है कि इसका वाष्पशीलता से कोई लेना-देना नहीं है - इसमें फ़ील्ड ऑब्जेक्ट करने के लिए लेखन के साथ कुछ करना है। मेरा झुकाव यह है कि यह किसी भी तरह एक स्मृति विवाद मुद्दा है - कैश और झूठी साझाकरण के साथ कुछ, हालांकि बिल्कुल स्पष्ट सिंक्रनाइज़ेशन नहीं है।

संपादित करें 2:

दिलचस्प है, इस तरह कार्यक्रम को बदलने:

final class Holder { 
    public Foo bar = null; 
} 

final class Reader extends Thread { 
    volatile Foo vfoo = new Foo(); 
    Holder holder = null; 
    int sz; 

    public Reader(int _sz) { 
     sz = _sz; 
    } 

    public void run() { 
     int i = 0; 
     holder = new Holder(); 
     while (i < sz) { 
      vfoo.x = 1; 
      holder.bar = vfoo; 
      i++; 
     } 
    } 
} 

स्केलिंग समस्या का समाधान। जाहिर है, Holder उपरोक्त ऑब्जेक्ट थ्रेड शुरू होने के बाद बनाया जाता है, और शायद स्मृति के एक अलग सेगमेंट में आवंटित किया जाता है, जिसे बाद में संशोधित किया जा रहा है, क्योंकि थ्रेड ऑब्जेक्ट में क्षेत्र bar को संशोधित करने के विपरीत, जो कि किसी भी तरह "बंद" है विभिन्न थ्रेड उदाहरणों के बीच स्मृति में।

+0

'बार = vfoo' धीमी है, क्योंकि यह एक अस्थिर पढ़ा है। गैर-अस्थिर vfoo (असाइनमेंट को असामान्य करने के विरोध में) के साथ आपको कितनी बार मिलता है? – Viruzzo

+1

1) 'vfoo.x = 1' भी अस्थिर पढ़ा जाता है, लेकिन यह धीमा नहीं है और यह अच्छी तरह से स्केल करता है। 2) जब 'vfoo' nonvolatile है, तो JIT लूप को दूर करता है, जब तक कि आप इसके प्रभावों का मुकाबला करने के लिए 'vfoo.x = 1' से पहले लूप के अंदर' if (bar! = Null) 'चेक जोड़ते हैं। यदि आप ऐसा करते हैं - 'vfoo' nonvolatile बनाएं और यह चेक जोड़ें, वही स्केलेबिलिटी समस्या बनी हुई है। – axel22

+0

@ axel22: मुझे नहीं लगता कि कैसे vfoo.x = 1' एक अस्थिर पढ़ा जाता है, आप केवल एक अस्थिर क्षेत्र में लिख रहे हैं। 'bar = vfoo' इसके बजाए एक अस्थिर संदर्भ पढ़ता है। – ninjalj

उत्तर

2

आप वास्तव में एक अस्थिर क्षेत्र में नहीं लिख रहे हैं, इसलिए प्रत्येक थ्रेड में अस्थिर क्षेत्र को कैश किया जा सकता है।

अस्थिरता का उपयोग कुछ संकलक अनुकूलन और माइक्रो-बेंचमार्क में रोकता है, तो आप एक बड़ा सापेक्ष अंतर देख सकते हैं।

उपर्युक्त उदाहरण में, टिप्पणी की गई संस्करण अधिक है क्योंकि इसमें लूप को एक वास्तविक पाश में दो पुनरावृत्तियों को स्थानांतरित करने के लिए अनलॉक किया गया है। यह लगभग दोहरा प्रदर्शन कर सकते हैं।

अस्थिरता का उपयोग करते समय आप देख सकते हैं कि कोई लूप अनोलिंग नहीं है।

बीटीडब्ल्यू: आप इसे पढ़ने में आसान बनाने के लिए अपने उदाहरण में बहुत से कोड को हटा सकते हैं। ;)

+0

धन्यवाद, मैंने कोड थोड़ा सा निकाला है। लेकिन: 1) प्रत्येक थ्रेड (रजिस्टरों में) में अस्थिर क्षेत्र को कैश करना अस्थिरता को हटाकर, 2) अस्थिरता को हटाकर, समस्या बनी रहती है, जैसा ऊपर दिए गए प्रश्न के बाद टिप्पणी में बताया गया है, 3) टिप्पणी (स्केलेबल) संस्करण छोटा है, लंबा नहीं, 4) जबकि विभिन्न लंबाई प्रदर्शन को प्रभावित कर सकती हैं (और वे करते हैं - 1 थ्रेड मामले में लगभग 50%), मुझे नहीं लगता कि यह स्केलेबिलिटी को कैसे प्रभावित कर सकता है। – axel22

+0

लोगों को प्रश्न पढ़ने के जवाब देने के लिए छोटा कोड आसान है। –

+0

@ पीटर लेवरी: मुझे लूप अनोलिंग नहीं दिख रहा है। – ninjalj

1

संपादित करें: यह उत्तर परीक्षण करने के लिए खड़ा नहीं था।

मेरे पास अभी इस परीक्षण का कोई तरीका नहीं है (इस मशीन में कोई मल्टीकोर सीपीयू नहीं), लेकिन यहां एक सिद्धांत है: Foo उदाहरण एक ही कैश लाइनों में नहीं हो सकते हैं, लेकिन शायद Reader उदाहरण हैं।

इसका मतलब यह है मंदी लिखने से, बल्कि foo के पढ़ने से, bar को समझाया जा सकता है क्योंकि bar के लिए लिख अन्य कोर के लिए है कि कैश लाइन को अमान्य और कैश के बीच नकल के बहुत सारे कारण होगा। bar पर लिखने पर टिप्पणी (जो लूप में Reader के क्षेत्र में केवल एकमात्र लिखना है) मंदी को रोकता है, जो इस स्पष्टीकरण के अनुरूप है।

संपादित करें: this article के अनुसार, वस्तुओं की स्मृति लेआउट ऐसी है कि bar संदर्भ Reader वस्तु के लेआउट में पिछले क्षेत्र होगा। इसका मतलब है कि हीप पर अगली वस्तु के रूप में एक ही कैश लाइन में जमीन के लिए संभव है। चूंकि मैं उस आदेश के बारे में निश्चित नहीं हूं जिसमें हेप पर नई वस्तुओं को आवंटित किया गया है, मैंने नीचे दिए गए टिप्पणी में सुझाव दिया है कि संदर्भों के साथ "गर्म" ऑब्जेक्ट प्रकारों को पैड करें, जो वस्तुओं को अलग करने में प्रभावी होगा (कम से कम, मुझे उम्मीद है कि यह होगा, लेकिन यह इस बात पर निर्भर करता है कि उसी प्रकार के फ़ील्ड मेमोरी में कैसे क्रमबद्ध होते हैं)।

+0

मैं इस सिद्धांत के लिए जाऊंगा, लेकिन मैंने शुरुआत में माना कि 'रीडर', 'थ्रेड' होने के कारण, बहुत सारे क्षेत्र हैं। फिर भी, इसे जांचने के लिए, मैंने अभी 16 32-बिट पूर्णांक फ़ील्ड जोड़े हैं। माप बिल्कुल वही रहता है। तो, यह मानते हुए कि ऑब्जेक्ट इंस्टेंस मेमोरी के संगत क्षेत्रों पर कब्जा करते हैं, यह कारण नहीं होना चाहिए। JVM पर मेमोरी लेआउट के बारे में एक ब्लॉग पोस्ट यहां इस धारणा के पक्ष में है: http://www.codeinstructions.com/2008/12/java-objects-memory- संरचना।एचटीएमएल – axel22

+0

क्या आप कुछ शून्य संदर्भों के साथ फू और रीडर दोनों को पैडिंग करने का प्रयास कर सकते हैं (आपके सिस्टम के आधार पर 4 या 8 बाइट गिनते हैं)? मेरे पास एक निराशाजनक सिद्धांत है जो धारणाओं पर बहुत अधिक आधारित है, लेकिन यह सिर्फ काम कर सकता है। – Medo42

+0

मैंने 'ऑब्जेक्ट एक्सएक्स', 'एक्स' के साथ दोनों वर्गों को '0' से 'f' तक (कुल में 16 रेफरी) तक पैड किया है। यह परिणाम नहीं बदला है। – axel22

3

यह मैं क्या सोचता है (ध्यान में रखना मैं हॉटस्पॉट से परिचित नहीं हूँ) हो रहा है:

0xf36c9fd0: mov 0x6c(%ecx),%ebp ; vfoo 
0xf36c9fd3: test %ebp,%ebp   ; vfoo is null? 
0xf36c9fd5: je  0xf36c9ff7   ; throw NullPointerException (I guess) 
0xf36c9fd7: movl $0x1,0x8(%ebp)  ; vfoo.x = 1 
0xf36c9fde: mov 0x68(%ecx),%ebp ; sz 
0xf36c9fe1: inc %ebx    ; i++ 
0xf36c9fe2: test %edi,0xf7725000 ; safepoint on end of loop 
0xf36c9fe8: cmp %ebp,%ebx   ; i < sz? 
0xf36c9fea: jl  0xf36c9fd0 


0xf3771ad0: mov 0x6c(%ecx),%ebp   ; vfoo 
0xf3771ad3: test %ebp,%ebp    ; vfoo is null? 
0xf3771ad5: je  0xf3771b09    ; throw NullPointerException (I guess) 
0xf3771ad7: movl $0x1,0x8(%ebp)   ; vfoo.x = 1 
0xf3771ade: mov 0x6c(%ecx),%ebp   ; \ 
0xf3771ae1: mov %ebp,0x70(%ecx)   ;/bar = vfoo 
0xf3771ae4: mov 0x68(%ecx),%edi   ; sz 
0xf3771ae7: inc %ebx      ; i++ 
0xf3771ae8: mov %ecx,%eax    ; 
0xf3771aea: shr $0x9,%eax    ; ??? \ Probably replaced later 
0xf3771aed: movb $0x0,-0x3113c300(%eax) ; ???/by some barrier code? 
0xf3771af4: test %edi,0xf77ce000   ; safepoint 
0xf3771afa: cmp %edi,%ebx    ; i < sz ? 
0xf3771afc: jl  0xf3771ad0    ; 

कारण मैं ऊपर कोड एक बाधा के लिए में खड़ा है कि जब NullPointerException लेने लगता है, स्केलेबल संस्करण में XCHG है, जो बाधा के रूप में कार्य करता है, जबकि गैर स्केलेबल संस्करण में एनओपी होता है।

तर्क यह होगा कि vfoo के शुरुआती लोड और थ्रेड में शामिल होने के बीच आदेश होने से पहले ऐसा होना आवश्यक है। अस्थिर मामले में, बाधा लूप के अंदर होगी, इसलिए इसे कहीं और होने की आवश्यकता नहीं होगी। मुझे समझ में नहीं आता है कि XCHG लूप के अंदर क्यों उपयोग नहीं किया जाता है। शायद MFENCE समर्थन का रनटाइम पता लगाना?

+0

जाहिर है, 'shr' /' movb' निर्देश जोड़ी बिल्कुल बाधा कोड है - यह कचरा कलेक्टर द्वारा उपयोग किए जाने वाले कार्ड गंदे बाइट को सेट करता है। – axel22

3

आइए JVM को थोड़ा और अधिक "लगातार" व्यवहार करने का प्रयास करें। जेआईटी कंपाइलर वास्तव में टेस्ट रन की तुलना को फेंक रहा है; तो चलिए -Djava.compiler=NONE का उपयोग कर दें। यह निश्चित रूप से एक प्रदर्शन हिट पेश करता है, लेकिन जेआईटी कंपाइलर अनुकूलन के अस्पष्टता और प्रभाव को खत्म करने में मदद करेगा।

कचरा संग्रह जटिलताओं का अपना सेट पेश करता है। -XX:+UseSerialGC का उपयोग करके serial garbage collector का उपयोग करें। आइए स्पष्ट कचरा संग्रह भी अक्षम करें और कचरा संग्रह कब किया जाता है यह देखने के लिए कुछ लॉगिंग चालू करें: -verbose:gc -XX:+DisableExplicitGC। अंत में, चलिए -Xmx128m -Xms128m का उपयोग करके आवंटित पर्याप्त ढेर प्राप्त करें।

अब हम का उपयोग कर परीक्षण चला सकते हैं:

java -XX:+UseSerialGC -verbose:gc -XX:+DisableExplicitGC -Djava.compiler=NONE -Xmx128m -Xms128m -server -Dsize=50000000 -Dpar=1 MultiVolatileJavaExperiment 10 

परीक्षण कई बार चल रहा है पता चलता परिणाम बहुत संगत कर रहे हैं (मैं Ubuntu 10.04.3 LTS पर ओरेकल जावा 1.6.0_24-B07 उपयोग कर रहा हूँ एक साथ इंटेल (आर) कोर (टीएम) 2 डुओ सीपीयू पी 8700 @ 2.53GHz), लगभग 2050 मिलीसेकंड औसत। अगर मैं bar = vfoo लाइन पर टिप्पणी करता हूं, तो मैं लगातार 1280 मिलीसेकंड औसत से औसत हूं। -Dpar=2 का उपयोग करके परीक्षण चलाकर bar = vfoo के साथ लगभग 1350 मिलीसेकंड के साथ परिणाम और इसके साथ लगभग 1005 मिलीसेकंड टिप्पणी की गई।

+=========+======+=========+ 
| Threads | With | Without | 
+=========+======+=========+ 
| 1 | 2050 | 1280 | 
+---------+------+---------+ 
| 2 | 1350 | 1005 | 
+=========+======+=========+ 

आइए अब कोड देखें और देखें कि क्या हम किसी भी कारण से स्पॉट कर सकते हैं कि बहु-थ्रेडिंग अक्षम क्यों है।

int i = 0; 
while (i < this.sz) { 
    this.vfoo.x = 1; 
    this.bar = this.vfoo; 
    i++; 
} 

प्रथम सूचना के लिए बात while पाश चार चर this के माध्यम से संदर्भित होता है: Reader.run() में, this के रूप में उपयुक्त के साथ चर योग्यता यह स्पष्ट है जो चर स्थानीय कर रहे हैं मदद मिलेगी। इसका मतलब है कि कोड कक्षा के रनटाइम निरंतर पूल तक पहुंच रहा है और टाइप-चेकिंग कर रहा है (getfield बाइटकोड निर्देश के माध्यम से)। आइए रनटाइम निरंतर पूल तक पहुंचने और देखने के लिए कोड को बदलें और देखें कि हमें कोई लाभ मिलता है या नहीं।

final int mysz = this.sz; 
int i = 0; 
while (i < mysz) { 
    this.vfoo.x = 1; 
    this.bar = this.vfoo; 
    i++; 
} 

यहाँ, हम पाश आकार तक पहुँचने के लिए एक स्थानीय mysz चर का उपयोग कर रहे हैं और केवल एक बार this के माध्यम से sz तक पहुँचने, आरंभीकरण के लिए। टेस्ट को दो थ्रेड के साथ चलाना, औसतन 12 9 5 मिलीसेकंड; एक छोटा सा लाभ, लेकिन फिर भी एक।

while लूप को देखते हुए, क्या हमें वास्तव में this.vfoo को दो बार संदर्भित करने की आवश्यकता है? दो अस्थिर पढ़ते हैं दो सिंक्रनाइज़ेशन किनारों को बनाते हैं कि वर्चुअल मशीन (और उस मामले के लिए अंतर्निहित हार्डवेयर) को प्रबंधित करने की आवश्यकता होती है। मान लीजिए कि हम while पाश की शुरुआत में एक तुल्यकालन बढ़त चाहते करते हैं और हम दो की जरूरत नहीं है, हम निम्नलिखित का उपयोग कर सकते: 1122 मिलीसेकेंड के बारे में

final int mysz = this.sz; 
Foo myvfoo = null; 
int i = 0; 
while (i < mysz) { 
    myvfoo = this.vfoo; 
    myvfoo.x = 1; 
    this.bar = myvfoo; 
    i++; 
} 

यह औसत; अभी भी बेहतर हो रहा है। उस this.bar संदर्भ के बारे में क्या? चूंकि हम बहु-थ्रेडिंग की बात कर रहे हैं, मान लीजिए कि while लूप में गणनाएं हम बहु-थ्रेडेड लाभ प्राप्त करना चाहते हैं और this.bar यह है कि हम अपने परिणामों को दूसरों के साथ कैसे संवाद करते हैं। while लूप होने के बाद तक हम वास्तव में this.bar सेट नहीं करना चाहते हैं।

final int mysz = this.sz; 
Foo myvfoo = null; 
Foo mybar = null; 
int i = 0; 
while (i < mysz) { 
    myvfoo = this.vfoo; 
    myvfoo.x = 1; 
    mybar = myvfoo; 
    i++; 
} 
this.bar = mybar; 

जो हमें औसतन 857 मिलीसेकंड देता है। while लूप में अभी भी अंतिम this.vfoo संदर्भ है। फिर से मान लें कि while लूप वह है जिसे हम बहु-थ्रेडेड लाभ चाहते हैं, चलिए while लूप से this.vfoo को स्थानांतरित करें।

final int mysz = this.sz; 
final Foo myvfoo = this.vfoo; 
Foo mybar = null; 
int i = 0; 
while (i < mysz) { 
    myvfoo.x = 1; 
    mybar = myvfoo; 
    i++; 
} 
final Foo vfoocheck = this.vfoo; 
if (vfoocheck != myvfoo) { 
    System.out.println("vfoo changed from " + myvfoo + " to " + vfoocheck); 
} 
this.bar = mybar; 

अब हम लगभग 502 मिलीसेकंड औसत करते हैं; सिंगल थ्रेडेड टेस्ट औसत 900 मिलीसेकंड औसत है।

तो यह हमें क्या बताता है? while लूप से गैर-स्थानीय परिवर्तनीय संदर्भों को extrapolating करके, सिंगल- और डबल थ्रेडेड परीक्षणों में दोनों महत्वपूर्ण प्रदर्शन लाभ रहे हैं।MultiVolatileJavaExperiment का मूल संस्करण गैर-स्थानीय चर 50,000,000 बार तक पहुंचने की लागत को माप रहा था, जबकि अंतिम संस्करण स्थानीय चर 50,000,000 बार तक पहुंचने की लागत को माप रहा है। स्थानीय चर का उपयोग करके, आप संभावना बनाते हैं कि जावा वर्चुअल मशीन और अंतर्निहित हार्डवेयर थ्रेड कैश को अधिक कुशलतापूर्वक प्रबंधित कर सकता है।

अंत में, के सामान्य रूप से का उपयोग कर परीक्षण चला है (ध्यान दें, 50,000,000 के बजाय 500,000,000 पाश आकार का उपयोग करते हुए) करते हैं:

java -Xmx128m -Xms128m -server -Dsize=500000000 -Dpar=2 MultiVolatileJavaExperiment 10 

मूल संस्करण का औसत 1100 मिलीसेकेंड और संशोधित संस्करण का औसत लगभग 10 मिलीसेकंड।

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