29

में सिंक्रनाइज़ेशन अनुभाग का प्रदर्शन जावा में सिंक्रनाइज़ किए गए ब्लॉक के प्रदर्शन पर मेरा एक छोटा विवाद था। यह सैद्धांतिक सवाल है, जो वास्तविक जीवन आवेदन को प्रभावित नहीं करता है। एकल-थ्रेडेड एप्लिकेशन पर विचार करें, जो ताले का उपयोग करता है और खंडों को सिंक्रनाइज़ करता है। क्या यह कोड खंडों को सिंक्रनाइज़ किए बिना एक ही कोड से धीमा काम करता है? यदि हां, तो क्यों? हम संगामिति चर्चा नहीं करते, क्योंकि यह केवल एकल थ्रेड आवेदनजावा

Upd

मिले दिलचस्प benchmark परीक्षण है। लेकिन यह 2001 से है। जेडीके

+0

अच्छा के रूप में, चीजों को विकसित किया है ** एक बहुत ** के बाद से यह दस साल पहले लिखा गया था। – NPE

+0

संक्षिप्त उत्तर: यह करता है! – bestsss

+0

लंबा उत्तर: हाँ। JVM को विकसित करने के बावजूद JVM को हमेशा यह हल करने की आवश्यकता होगी कि ऑब्जेक्ट की कुंजी उपलब्ध है या नहीं। –

उत्तर

27

हॉटस्पॉट

  1. फैट में ताला लगा के 3 प्रकार के होते हैं: JVM ताला प्राप्त करने के लिए ओएस mutexes पर निर्भर करता है।
  2. पतला: जेवीएम सीएएस एल्गोरिदम का उपयोग कर रहा है।
  3. पक्षपातपूर्ण: सीएएस कुछ वास्तुकला पर महंगे ऑपरेशन है। पक्षपातपूर्ण लॉकिंग - विशेष प्रकार के लॉकिंग को परिदृश्य के लिए अनुकूलित किया जाता है जब ऑब्जेक्ट पर केवल एक थ्रेड काम कर रहा होता है।

डिफ़ॉल्ट JVM द्वारा उपयोग करता पतली ताला। बाद में यदि JVM निर्धारित करता है कि कोई विवाद नहीं है पतली लॉकिंग पक्षपातपूर्ण लॉकिंग में परिवर्तित हो गई है। ऑपरेशन जो लॉक के प्रकार को बदलता है वह महंगा है, इसलिए JVM तुरंत इस अनुकूलन को लागू नहीं करता है। विशेष JVM विकल्प है - XX: BiasedLockingStartupDelay = देरी जो JVM को बताता है जब इस तरह के अनुकूलन को लागू किया जाना चाहिए।

एक बार पूर्वाग्रहित होने के बाद, वह थ्रेड बाद में महंगा परमाणु निर्देशों का उपयोग किए बिना ऑब्जेक्ट को लॉक और अनलॉक कर सकता है।

प्रश्न का उत्तर: यह निर्भर करता है। लेकिन यदि पक्षपातपूर्ण है, तो लॉकिंग के बिना सिंगल थ्रेडेड कोड और लॉकिंग के बिना औसत समान प्रदर्शन होता है।

+4

बहुत जानकारीपूर्ण । हालांकि आप जावा/वीएम के किस संस्करण के लिए यह उत्तर दे सकते हैं कि यह उत्तर लिखा गया है? –

17

के नवीनतम संस्करण में चीजें नाटकीय रूप से बदल सकतीं, एक गैर-प्रतियोगिता वाले लॉक को प्राप्त करने में कुछ ओवरहेड है, लेकिन आधुनिक जेवीएम पर यह बहुत छोटा है।

इस मामले के लिए प्रासंगिक एक महत्वपूर्ण रन-टाइम ऑप्टिमाइज़ेशन को "बाईज्ड लॉकिंग" कहा जाता है और Java SE 6 Performance White Paper में समझाया गया है।

यदि आप कुछ जेवीएम और हार्डवेयर के लिए प्रासंगिक प्रदर्शन संख्याएं चाहते हैं, तो आप इस ओवरहेड को आजमाने और मापने के लिए माइक्रो-बेंचमार्क बना सकते हैं।

+5

मैंने इसका परीक्षण किया। यह इतना छोटा है कि आप प्रभाव को माप नहीं सकते हैं। वे कहते हैं कि प्रभाव JVM के पुराने संस्करणों के लिए अधिक महत्वपूर्ण था। – AlexR

+0

@AlexR: अच्छा, साझा करने के लिए धन्यवाद। यह मुझे आश्चर्य नहीं करता है कि प्रभाव अधिक महत्वपूर्ण होता था, क्योंकि पक्षपातपूर्ण लॉकिंग अनुकूलन केवल जावा 6 में जोड़ा गया था। – NPE

+4

* इतना छोटा है कि आप सभी को इस तरह के प्रभाव को माप नहीं सकते * ऐसे दावे को हल्के ढंग से नहीं बनाया जा सकता है। जब एक तंग पाश में कुछ परीक्षण करते हैं, तो जेवीएम महान जादूगर कर सकता है। लेकिन यह "वास्तविक दुनिया" ऐप्स का प्रतिनिधित्व नहीं करता है। जब निष्पादन जटिल हो जाता है तो JVM वास्तव में बेवकूफ हो जाता है। – irreputable

-1

मान लें कि आप हॉटस्पॉट वीएम का उपयोग कर रहे हैं, मेरा मानना ​​है कि जेवीएम यह पहचानने में सक्षम है कि synchronized ब्लॉक के भीतर किसी भी संसाधन के लिए कोई विवाद नहीं है और इसे "सामान्य" कोड के रूप में देखें।

+3

उद्धरण, कृपया। मुझे नहीं लगता कि जेवीएम मॉनिटर प्रविष्टियों को खत्म कर सकता है और पूरी तरह से बाहर निकल सकता है। – erickson

+0

मैंने इसे कहीं भी पढ़ा है। यदि हॉटस्पॉट कंपाइलर सुनिश्चित है कि कोड केवल एक थ्रेड से पहुंच योग्य है, तो इसे सिंक्रनाइज़ेशन को पूरी तरह से छोड़ना चाहिए।मैं निश्चित रूप से "निश्चित है ..." भाग के बारे में बिल्कुल निश्चित नहीं हूं और मैंने वास्तव में वीएम को ऐसा करने में कामयाब नहीं किया है। यहां तक ​​कि एकल-थ्रेड एप्लिकेशन में, सिंक्रनाइज़ेशन ओवरहेड को कम करके आंका नहीं जाना चाहिए। – jarnbjo

+0

सुनिश्चित नहीं हैं, कि यह संभव है JVM इस अनुकूलन – Anton

8

ताले का उपयोग करते समय आपको अपने आवेदन को धीमा करने की आवश्यकता नहीं है। यह मापने के लिए बहुत छोटा हो सकता है या यह आश्चर्यजनक रूप से उच्च हो सकता है।

आईएमएचओ अक्सर सबसे अच्छा तरीका एक थ्रेडेड प्रोग्राम में लॉक फ्री कोड का उपयोग करना है ताकि यह स्पष्ट हो सके कि यह कोड थ्रेड में साझा करने का इरादा नहीं है। यह किसी भी प्रदर्शन के मुद्दों की तुलना में रखरखाव के लिए और अधिक महत्वपूर्ण हो सकता है।

public static void main(String... args) throws IOException { 
    for (int i = 0; i < 3; i++) { 
     perfTest(new Vector<Integer>()); 
     perfTest(new ArrayList<Integer>()); 
    } 
} 

private static void perfTest(List<Integer> objects) { 
    long start = System.nanoTime(); 
    final int runs = 100000000; 
    for (int i = 0; i < runs; i += 20) { 
     // add items. 
     for (int j = 0; j < 20; j+=2) 
      objects.add(i); 
     // remove from the end. 
     while (!objects.isEmpty()) 
      objects.remove(objects.size() - 1); 
    } 
    long time = System.nanoTime() - start; 
    System.out.printf("%s each add/remove took an average of %.1f ns%n", objects.getClass().getSimpleName(), (double) time/runs); 
} 

प्रिंट

Vector each add/remove took an average of 38.9 ns 
ArrayList each add/remove took an average of 6.4 ns 
Vector each add/remove took an average of 10.5 ns 
ArrayList each add/remove took an average of 6.2 ns 
Vector each add/remove took an average of 10.4 ns 
ArrayList each add/remove took an average of 5.7 ns 

देखने के एक प्रदर्शन बिंदु से, अगर 4 एनएस आप के लिए महत्वपूर्ण है, तो आप गैर सिंक्रनाइज़ संस्करण का उपयोग करने के लिए है।

99% उपयोग मामलों के लिए, कोड की स्पष्टता प्रदर्शन की तुलना में अधिक महत्वपूर्ण है। साफ़, सरल कोड अक्सर उचित रूप से अच्छा प्रदर्शन करता है।

बीटीडब्लू: मैं ओरेकल जावा 7u1 के साथ 4.6 गीगाहर्ट्ज i7 2600 का उपयोग कर रहा हूं।


तुलना करने के लिए यदि मैं निम्नलिखित करता हूं तो perfTest1,2,3 समान हैं।

perfTest1(new ArrayList<Integer>()); 
    perfTest2(new Vector<Integer>()); 
    perfTest3(Collections.synchronizedList(new ArrayList<Integer>())); 

मैं

ArrayList each add/remove took an average of 2.6 ns 
Vector each add/remove took an average of 7.5 ns 
SynchronizedRandomAccessList each add/remove took an average of 8.9 ns 

मिलता है मैं एक आम perfTest विधि का उपयोग यह कोड के रूप में बेहतर अनुरूप नहीं कर सकते हैं और वे कर रहे हैं सभी धीमी

ArrayList each add/remove took an average of 9.3 ns 
Vector each add/remove took an average of 12.4 ns 
SynchronizedRandomAccessList each add/remove took an average of 13.9 ns 

परीक्षण के आदेश गमागमन

ArrayList each add/remove took an average of 3.0 ns 
Vector each add/remove took an average of 39.7 ns 
ArrayList each add/remove took an average of 2.0 ns 
Vector each add/remove took an average of 4.6 ns 
ArrayList each add/remove took an average of 2.3 ns 
Vector each add/remove took an average of 4.5 ns 
ArrayList each add/remove took an average of 2.3 ns 
Vector each add/remove took an average of 4.4 ns 
ArrayList each add/remove took an average of 2.4 ns 
Vector each add/remove took an average of 4.6 ns 

एक समय

ArrayList each add/remove took an average of 3.0 ns 
ArrayList each add/remove took an average of 3.0 ns 
ArrayList each add/remove took an average of 2.3 ns 
ArrayList each add/remove took an average of 2.2 ns 
ArrayList each add/remove took an average of 2.4 ns 

और

Vector each add/remove took an average of 28.4 ns 
Vector each add/remove took an average of 37.4 ns 
Vector each add/remove took an average of 7.6 ns 
Vector each add/remove took an average of 7.6 ns 
Vector each add/remove took an average of 7.6 ns 
+0

मैं एक आईबीएम JDK पर और पहली बार चलाने वेक्टर और ArrayList के अलावा यह परीक्षण किया मेरी मशीन (54ns 48-50ns बनाम) पर 10% प्रदर्शन अंतर के बारे में है। मैंने इसे संग्रह। सिंक्रनाइज़लिस्ट के साथ भी परीक्षण किया और इसके खराब परफॉर्मेंस से आश्चर्यचकित हुआ। यह वेक्टर/ऐरेलिस्ट (110ns) के रूप में लगभग दोगुना धीमा था। – Stefan

+0

माइक्रो-ट्यूनिंग के बारे में चिंतित होने का यह एक और कारण है। एक अलग सिस्टम, हार्डवेयर, जेवीएम का उपयोग करके आप एक अलग परिणाम दे सकते हैं। –

+0

btw, इस तरह कोड पहले वेक्टर के लिए, कॉल लक्ष्य के बाद से (सूची ) परिवर्तन अनुकूलित है तो deoptimized और अनुकूलित फिर से,। चूंकि उचित deoptimization के बारे में सुनिश्चित नहीं हो सकता है (केवल वेक्टर + जाल के लिए कॉल संरक्षित किया जा सकता है) ArrayList मामले भुगतना होगा। क्या आप परीक्षण स्वैप कर सकते हैं, यानी ArrayList तो वेक्टर। अधिक उत्सुक ओटीओएच मामला खूनी सही पूर्वाग्रह लॉकिंग परीक्षण भी है। इसके अलावा कैस अपने CPU पर काफी सस्ती है, पुराने आर्किटेक्चर पर कैस – bestsss

42

एकल पिरोया कोड से कम एक अभी भी जब synchronized ब्लॉकों का उपयोग कर धीमा चलेगा। जाहिर है कि अन्य धागे खत्म होने की प्रतीक्षा करते समय आपको अन्य धागे रुकने नहीं होंगे, हालांकि आपको सिंक्रनाइज़ेशन के अन्य प्रभावों, जैसे कैश कोहेरेंसी का सामना करना पड़ेगा।

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

तो भले ही आप लॉक विवाद से मुक्त हों, फिर भी JVM को डेटा को मुख्य स्मृति में फ़्लश करने में हाउसकीपिंग करना होगा।

इसके अलावा, इसमें अनुकूलन बाधाएं हैं। JVM आदेश अनुकूलन प्रदान करने के लिए दिए गए निर्देशों का क्रम बदलने के लिए नि: शुल्क है:

foo++; 
bar++; 

बनाम: एक साधारण उदाहरण पर विचार

foo++; 
synchronized(obj) 
{ 
    bar++; 
} 

पहले उदाहरण में, संकलक पर foo और bar लोड करने के लिए नि: शुल्क है एक ही समय, फिर दोनों को बढ़ाएं, फिर उन्हें दोनों सहेजें। दूसरे उदाहरण में, कंपाइलर foo पर लोड/एड/सेव करना चाहिए, फिर bar पर लोड/एड/सेव करें। इस प्रकार, सिंक्रनाइज़ेशन निर्देशों को अनुकूलित करने के लिए जेआरई की क्षमता को प्रभावित कर सकता है।

(एक जावा मेमोरी मॉडल पर उत्कृष्ट पुस्तक ब्रायन गोएज़ के Java Concurrency In Practice है।)

0

यह नमूना कोड (100 धागे 1000000 पुनरावृत्तियों हर एक बनाने के साथ) से बचने और एक से परहेज नहीं के प्रदर्शन के बीच का अंतर बताता है सिंक्रनाइज़ ब्लॉक।

आउटपुट:

Total time(Avoid Sync Block): 630ms 
Total time(NOT Avoid Sync Block): 6360ms 
Total time(Avoid Sync Block): 427ms 
Total time(NOT Avoid Sync Block): 6636ms 
Total time(Avoid Sync Block): 481ms 
Total time(NOT Avoid Sync Block): 5882ms 

कोड: उस लेख है

import org.apache.commons.lang.time.StopWatch; 

public class App { 
    public static int countTheads = 100; 
    public static int loopsPerThead = 1000000; 
    public static int sleepOfFirst = 10; 

    public static int runningCount = 0; 
    public static Boolean flagSync = null; 

    public static void main(String[] args) 
    {   
     for (int j = 0; j < 3; j++) {  
      App.startAll(new App.AvoidSyncBlockRunner(), "(Avoid Sync Block)"); 
      App.startAll(new App.NotAvoidSyncBlockRunner(), "(NOT Avoid Sync Block)"); 
     } 
    } 

    public static void startAll(Runnable runnable, String description) { 
     App.runningCount = 0; 
     App.flagSync = null; 
     Thread[] threads = new Thread[App.countTheads]; 

     StopWatch sw = new StopWatch(); 
     sw.start(); 
     for (int i = 0; i < threads.length; i++) { 
      threads[i] = new Thread(runnable); 
     } 
     for (int i = 0; i < threads.length; i++) { 
      threads[i].start(); 
     } 
     do { 
      try { 
       Thread.sleep(10); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } while (runningCount != 0); 
     System.out.println("Total time"+description+": " + (sw.getTime() - App.sleepOfFirst) + "ms"); 
    } 

    public static void commonBlock() { 
     String a = "foo"; 
     a += "Baa"; 
    } 

    public static synchronized void incrementCountRunning(int inc) { 
     runningCount = runningCount + inc; 
    } 

    public static class NotAvoidSyncBlockRunner implements Runnable { 

     public void run() { 
      App.incrementCountRunning(1); 
      for (int i = 0; i < App.loopsPerThead; i++) { 
       synchronized (App.class) { 
        if (App.flagSync == null) { 
         try { 
          Thread.sleep(App.sleepOfFirst); 
         } catch (InterruptedException e) { 
          e.printStackTrace(); 
         } 
         App.flagSync = true; 
        } 
       } 
       App.commonBlock(); 
      } 
      App.incrementCountRunning(-1); 
     } 
    } 

    public static class AvoidSyncBlockRunner implements Runnable { 

     public void run() { 
      App.incrementCountRunning(1); 
      for (int i = 0; i < App.loopsPerThead; i++) { 
       // THIS "IF" MAY SEEM POINTLESS, BUT IT AVOIDS THE NEXT 
       //ITERATION OF ENTERING INTO THE SYNCHRONIZED BLOCK 
       if (App.flagSync == null) { 
        synchronized (App.class) { 
         if (App.flagSync == null) { 
          try { 
           Thread.sleep(App.sleepOfFirst); 
          } catch (InterruptedException e) { 
           e.printStackTrace(); 
          } 
          App.flagSync = true; 
         } 
        } 
       } 
       App.commonBlock(); 
      } 
      App.incrementCountRunning(-1); 
     } 
    } 
}