2010-07-22 9 views
6

जोशुआ ब्लोच का "प्रभावी जावा", आइटम 51 थ्रेड शेड्यूलर के आधार पर नहीं है और साथ ही चलने योग्य स्थिति में धागे को अनावश्यक रूप से नहीं रखता है। उद्धरित पाठ:जावा कंसुरेंसी जेडीके 1.6: व्यस्त इंतजार सिग्नलिंग से बेहतर है? प्रभावी जावा # 51

runnable थ्रेड की संख्या नीचे रखने के लिए मुख्य तकनीक प्रत्येक थ्रेड काम की एक छोटी राशि करते हैं और फिर कुछ हालत Object.wait या का उपयोग कर बीतने वाले कुछ समय के लिए उपयोग करने के लिए इंतजार करना है Thread.Sleep। थ्रेड को व्यस्त नहीं होना चाहिए-प्रतीक्षा करें, बार-बार डेटा संरचना की जांच करने के लिए प्रतीक्षा करें। प्रोग्राम को शेड्यूलर की अनियमितता के लिए कमजोर बनाने के अलावा, व्यस्त-प्रतीक्षा प्रोसेसर पर लोड को काफी बढ़ा सकती है, अन्य प्रक्रियाओं को उसी मशीन पर पूरा करने वाले उपयोगी काम की मात्रा को कम कर सकती है।

और फिर सिग्नल का सही उपयोग करके एक व्यस्त प्रतीक्षा के माइक्रोबेंमार्क को दिखाने के लिए चला जाता है। पुस्तक में, व्यस्त प्रतीक्षा 17 राउंड ट्रिप/एस करता है जबकि प्रतीक्षा/अधिसूचना संस्करण प्रति सेकेंड 23,000 राउंड ट्रिप करता है।

हालांकि, जब मैंने जेडीके 1.6 पर एक ही बेंचमार्क की कोशिश की, तो मैं सिर्फ विपरीत दिखता हूं - व्यस्त प्रतीक्षा 760 के राउंडट्रिप्स/सेकेंड करता है जबकि प्रतीक्षा/अधिसूचना संस्करण 53.3K राउंडट्रिप्स/एस है - यानी, प्रतीक्षा/सूचित करें ~ 1400 गुना तेज हो गया है, लेकिन ~ 13 गुना धीमा हो गया है?

मैं समझता हूं कि व्यस्त प्रतीक्षा अच्छी नहीं है और सिग्नलिंग अभी भी बेहतर है - सीपीयू उपयोग व्यस्त प्रतीक्षा संस्करण पर ~ 50% है जबकि यह प्रतीक्षा/अधिसूचना संस्करण पर ~ 30% पर रहता है - लेकिन क्या कुछ ऐसा है जो बताता है संख्याएँ?

यदि यह मदद करता है, तो मैं विन 7 x64 (कोर i5) पर जेडीके 1.6 (32 बिट) चला रहा हूं।

अद्यतन: नीचे स्रोत। व्यस्त कार्य बेंच चलाने के लिए, पिंगपोंगक्यूयू की बेस क्लास को BusyWorkQueue आयात java.util.LinkedList में बदलें; आयात java.util.List;

abstract class SignalWorkQueue { 
    private final List queue = new LinkedList(); 
    private boolean stopped = false; 

    protected SignalWorkQueue() { new WorkerThread().start(); } 

    public final void enqueue(Object workItem) { 
     synchronized (queue) { 
      queue.add(workItem); 
      queue.notify(); 
     } 
    } 

    public final void stop() { 
     synchronized (queue) { 
      stopped = true; 
      queue.notify(); 
     } 
    } 
    protected abstract void processItem(Object workItem) 
     throws InterruptedException; 
    private class WorkerThread extends Thread { 
     public void run() { 
      while (true) { // Main loop 
       Object workItem = null; 
       synchronized (queue) { 
        try { 
         while (queue.isEmpty() && !stopped) 
          queue.wait(); 
        } catch (InterruptedException e) { 
         return; 
        } 
        if (stopped) 
         return; 
        workItem = queue.remove(0); 
       } 
       try { 
        processItem(workItem); // No lock held 
       } catch (InterruptedException e) { 
        return; 
       } 
      } 
     } 
    } 
} 

// HORRIBLE PROGRAM - uses busy-wait instead of Object.wait! 
abstract class BusyWorkQueue { 
    private final List queue = new LinkedList(); 
    private boolean stopped = false; 

    protected BusyWorkQueue() { 
     new WorkerThread().start(); 
    } 

    public final void enqueue(Object workItem) { 
     synchronized (queue) { 
      queue.add(workItem); 
     } 
    } 

    public final void stop() { 
     synchronized (queue) { 
      stopped = true; 
     } 
    } 

    protected abstract void processItem(Object workItem) 
      throws InterruptedException; 

    private class WorkerThread extends Thread { 
     public void run() { 
      final Object QUEUE_IS_EMPTY = new Object(); 
      while (true) { // Main loop 
       Object workItem = QUEUE_IS_EMPTY; 
       synchronized (queue) { 
        if (stopped) 
         return; 
        if (!queue.isEmpty()) 
         workItem = queue.remove(0); 
       } 

       if (workItem != QUEUE_IS_EMPTY) { 
        try { 
         processItem(workItem); 
        } catch (InterruptedException e) { 
         return; 
        } 
       } 
      } 
     } 
    } 
} 

class PingPongQueue extends SignalWorkQueue { 
    volatile int count = 0; 

    protected void processItem(final Object sender) { 
     count++; 
     SignalWorkQueue recipient = (SignalWorkQueue) sender; 
     recipient.enqueue(this); 
    } 
} 

public class WaitQueuePerf { 
    public static void main(String[] args) { 
     PingPongQueue q1 = new PingPongQueue(); 
     PingPongQueue q2 = new PingPongQueue(); 
     q1.enqueue(q2); // Kick-start the system 

     // Give the system 10 seconds to warm up 
     try { 
      Thread.sleep(10000); 
     } catch (InterruptedException e) { 
     } 

     // Measure the number of round trips in 10 seconds 
     int count = q1.count; 
     try { 
      Thread.sleep(10000); 
     } catch (InterruptedException e) { 
     } 
     System.out.println(q1.count - count); 

     q1.stop(); 
     q2.stop(); 
    } 
} 

उत्तर

6

अपने परीक्षण में, कतार लगातार नए आइटम हो जाता है, इसलिए व्यस्त प्रतीक्षा बहुत कम वास्तविक प्रतीक्षा करता है।

यदि कतार में प्रत्येक 1ms की एक नई वस्तु मिलती है, तो आप व्यस्त-प्रतीक्षा को अधिकतर समय तक सीपीयू को जलाने में व्यतीत करेंगे। यह आवेदन के दूसरे हिस्से को धीमा कर देगा।

तो यह निर्भर करता है। यदि आप उपयोगकर्ता इनपुट पर प्रतीक्षा करते हैं, तो यह निश्चित रूप से गलत है; जबकि परमाणु इंटेगर जैसे लॉकलेस डेटास्ट्रक्चर में व्यस्त-प्रतीक्षा निश्चित रूप से अच्छी है।

+0

मुझे लगता है कि यह है। बस कोशिश की। दूसरी कतार में आइटम को छोड़ने से पहले 1 एमएम नींद के साथ पेश किया गया, दोनों रन काफी समान होते हैं - लगभग 400 राउंडट्रिप्स/एस। जैसा कि अपेक्षित है, व्यस्त प्रतीक्षा सीपीयू के 3 गुना अधिक उपयोग करता है। धन्यवाद! – Raghu

3

हाँ, व्यस्त इंतजार और अधिक तेजी से जवाब देंगे और अधिक छोरों पर अमल, लेकिन मुझे लगता है कि बिंदु यह पूरे सिस्टम पर एक disproportionally भारी बोझ डालता है कि था।

1000 व्यस्त प्रतीक्षा थ्रेड बनाम 1000 प्रतीक्षा/थ्रेड को सूचित करने का प्रयास करें और अपना कुल थ्रूपुट जांचें।

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

+0

ठीक है, पुस्तक यह सुझाव देती है कि आप व्यस्त प्रतीक्षाों का उपयोग करके हमेशा भुगतान करते हैं - यहां तक ​​कि जब आपके पास केवल कुछ धागे होते हैं। इसके अलावा, उद्धृत संख्याएं केवल उस संकेत को इंगित करती हैं। मैं बढ़ते उपयोग को देखता हूं और निश्चित रूप से समझता हूं कि क्या होगा यदि आपके पास पर्याप्त अन्य धागे हैं। तो - अभी भी परेशान है। – Raghu

1

यह धागे की मात्रा और संघर्ष की डिग्री पर निर्भर करता है: व्यस्त होने पर अक्सर प्रतीक्षा होती है, और अक्सर कई CPU चक्रों का उपभोग करते हैं।

लेकिन परमाणु इंटीग्रर्स (परमाणु इंटेगर, परमाणु इंटेगरएरे ...) एक इंटीजर या int [] सिंक्रनाइज़ करने से बेहतर हैं, यहां तक ​​कि थ्रेड भी व्यस्त प्रतीक्षा करता है।

java.util.concurrent पैकेज का प्रयोग करें और अपने मामले ConcurrentLinkedQueueas अक्सर के रूप में संभव

0

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

वास्तव में, यदि आप java.util.concurrent पैकेज स्रोतों के माध्यम से पैदल चलते हैं, तो आपको "मुश्किल", प्रतीत होता है कि नाजुक कोड वाले कई स्थान पाएंगे। मुझे SynchronousQueue एक अच्छा उदाहरण मिलता है (आप जेडीके वितरण या here में स्रोत पर एक नज़र डाल सकते हैं, ओपनजेडीके और ओरेकल दोनों एक ही कार्यान्वयन का उपयोग करते हैं)। व्यस्त प्रतीक्षा का उपयोग अनुकूलन के रूप में किया जाता है - "स्पिन" की कुछ मात्रा के बाद, धागा उचित "नींद" में जाता है। इसके अलावा, इसमें कुछ अन्य नस्लों के साथ-साथ अस्थिर पिगबैकिंग, स्पिन ट्रेसहोल्ड CPUs की संख्या पर निर्भर है। यह वास्तव में ... रोशनी है, जिसमें यह दिखाता है कि कुशल कम-स्तरीय समेकन को लागू करने के लिए क्या होता है। इससे भी बेहतर, कोड स्वयं वास्तव में साफ, अच्छी तरह से प्रलेखित और उच्च गुणवत्ता वाला है।

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