2011-08-23 17 views
5

मैं एक साधारण गणना निष्पादित करने की कोशिश कर रहा हूं (यह Math.random() 10000000 बार कॉल करता है)। आश्चर्यजनक रूप से इसे सरल विधि में चलाना निष्पादक सेवा का उपयोग करने से बहुत तेज प्रदर्शन करता है।निष्पादक सेवा धीमी बहु थ्रेड प्रदर्शन

मैं ExecutorService's surprising performance break-even point --- rules of thumb? पर एक और धागा पढ़ लिया है और Callable बैचों का उपयोग कर क्रियान्वित करते हुए जवाब का पालन करने की कोशिश की, लेकिन प्रदर्शन मेरे वर्तमान कोड के आधार पर अभी भी बुरा

मैं प्रदर्शन में सुधार कैसे करना है?

import java.util.*; 
import java.util.concurrent.*; 

public class MainTest { 
    public static void main(String[]args) throws Exception { 
     new MainTest().start();; 
    } 

    final List<Worker> workermulti = new ArrayList<Worker>(); 
    final List<Worker> workersingle = new ArrayList<Worker>(); 
    final int count=10000000; 

    public void start() throws Exception { 
     int n=2; 

     workersingle.add(new Worker(1)); 
     for (int i=0;i<n;i++) { 
      // worker will only do count/n job 
      workermulti.add(new Worker(n)); 
     } 

     ExecutorService serviceSingle = Executors.newSingleThreadExecutor(); 
     ExecutorService serviceMulti = Executors.newFixedThreadPool(n); 
     long s,e; 
     int tests=10; 
     List<Long> simple = new ArrayList<Long>(); 
     List<Long> single = new ArrayList<Long>(); 
     List<Long> multi = new ArrayList<Long>(); 

     for (int i=0;i<tests;i++) { 
      // simple 
      s = System.currentTimeMillis(); 
      simple(); 
      e = System.currentTimeMillis(); 
      simple.add(e-s); 

      // single thread 
      s = System.currentTimeMillis(); 
       serviceSingle.invokeAll(workersingle); // single thread 
      e = System.currentTimeMillis(); 
      single.add(e-s); 

      // multi thread 
      s = System.currentTimeMillis(); 
       serviceMulti.invokeAll(workermulti); 
      e = System.currentTimeMillis(); 
      multi.add(e-s); 
     } 
     long avgSimple=sum(simple)/tests; 
     long avgSingle=sum(single)/tests; 
     long avgMulti=sum(multi)/tests; 
     System.out.println("Average simple: "+avgSimple+" ms"); 
     System.out.println("Average single thread: "+avgSingle+" ms"); 
     System.out.println("Average multi thread: "+avgMulti+" ms"); 

     serviceSingle.shutdown(); 
     serviceMulti.shutdown(); 
    } 

    long sum(List<Long> list) { 
     long sum=0; 
     for (long l : list) { 
      sum+=l; 
     } 
     return sum; 
    } 

    private void simple() { 
     for (int i=0;i<count;i++){ 
      Math.random(); 
     } 
    } 

    class Worker implements Callable<Void> { 
     int n; 

     public Worker(int n) { 
      this.n=n; 
     } 

     @Override 
     public Void call() throws Exception { 
      // divide count with n to perform batch execution 
      for (int i=0;i<(count/n);i++) { 
       Math.random(); 
      } 
      return null; 
     } 
    } 
} 

इस कोड

Average simple: 920 ms 
Average single thread: 1034 ms 
Average multi thread: 1393 ms 

संपादित करें के लिए उत्पादन: प्रदर्शन math.random (की वजह से पीड़ित हैं) किया जा रहा है एक तुल्यकालन विधि .. प्रत्येक थ्रेड के लिए नए रैंडम वस्तु के साथ math.random() बदलने के बाद , प्रदर्शन

नए कोड के लिए उत्पादन में सुधार (प्रत्येक थ्रेड के लिए रैंडम साथ math.random() बदलने के बाद)

Average simple: 928 ms 
Average single thread: 1046 ms 
Average multi thread: 642 ms 

उत्तर

12

Math.random() सिंक्रनाइज़ किया गया है। सिंक्रनाइज़ किए गए पूरे बिंदु की तरह चीजों को धीमा करना है ताकि वे टकरा न जाए। कुछ ऐसा उपयोग करें जो सिंक्रनाइज़ नहीं है और/या प्रत्येक थ्रेड को अपनी नई वस्तु को काम करने के लिए दें, जैसे कि Random

+0

आह आप सही हैं! मुझे एहसास नहीं हुआ कि मैथ। यादृच्छिक() सिंक्रनाइज़ किया गया है .. एक बार जब मैं प्रत्येक वर्कर के लिए नया रैंडम ऑब्जेक्ट डालता हूं, तो प्रदर्शन में काफी सुधार हुआ – GantengX

+0

बस एक त्वरित सवाल, अगर मैंने यादृच्छिक वस्तु को साझा करने का प्रयास किया, तो प्रदर्शन अभी भी पीड़ित है। क्या आप जानते हैं कि ऐसा क्यों है? Random.nextDouble सिंक्रनाइज़ नहीं किया गया है और यह Random.next (int) को कॉल करता है जो बदले में AtomicLong.compareAndSet को कॉल करता है ..मुझे नहीं लगता कि यह प्रदर्शन – GantengX

+4

को क्यों प्रभावित करेगा, मुझे लगता है कि आप एक ही संसाधन के लिए फिर से कई धागे का विरोध करने के लिए वापस आ गए हैं: इस मामले में परमाणु लोंग। केवल एक धागा एक समय में अपना मान अपडेट कर सकता है, और प्रत्येक कॉल के लिए इसे दो बार अपडेट किया जाता है()। –

3

आप अन्य धागे की सामग्री को पढ़ने के लिए अच्छी तरह से करना चाहते हैं। वहां बहुत अच्छी युक्तियां हैं।

शायद आपके बेंचमार्क के साथ सबसे महत्वपूर्ण मुद्दा यह है कि Math.random() अनुबंध के अनुसार, "यह विधि ठीक से एक से अधिक धागे द्वारा सही उपयोग की अनुमति देने के लिए सिंक्रनाइज़ है। हालांकि, अगर कई धागे छद्म यादृच्छिक संख्या उत्पन्न करने की आवश्यकता है एक महान दर पर, यह प्रत्येक धागे के लिए अपने स्वयं के छद्म यादृच्छिक संख्या जनरेटर के लिए विवाद को कम कर सकता है "

इस प्रकार पढ़ें: विधि सिंक्रनाइज़ है, इसलिए केवल एक थ्रेड उपयोगी रूप से इसका उपयोग करने में सक्षम होने की संभावना है पहर। तो आप कार्यों को वितरित करने के लिए ओवरहेड का एक गुच्छा करते हैं, केवल उन्हें क्रमशः चलाने के लिए मजबूर करने के लिए।

1

जब आप एकाधिक धागे का उपयोग करते हैं, तो आपको अतिरिक्त धागे का उपयोग करने के ऊपरी हिस्से से अवगत होना चाहिए। आपको यह निर्धारित करने की भी आवश्यकता है कि आपके एल्गोरिदम के पास काम है या नहीं, जिसे समानांतर में पूर्ववर्ती किया जा सकता है या नहीं। तो आपको ऐसे काम की ज़रूरत है जो एक साथ चल सकें जो काफी बड़ा है कि यह एकाधिक धागे का उपयोग करने के ऊपरी हिस्से से अधिक हो जाएगा।

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

आखिरकार यदि आप बहुत से नंबरों को जोड़ना चाहते हैं, तो आपको उन्हें सहेजने और उन्हें बाद में योग करने की आवश्यकता नहीं है। जब आप जाते हैं तो आप उन्हें जोड़ सकते हैं।

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