2012-05-22 20 views
7

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

void doSearch() throws ExecutionException, InterruptedException { 
    final int numProc = Runtime.getRuntime().availableProcessors(); 
    System.out.println("numProc = " + numProc); 
    ExecutorService executor = Executors.newFixedThreadPool(numProc); 
    long currTime = System.currentTimeMillis(); 
    List<Future<ValueBean>> list = new ArrayList<Future<ValueBean>>(); 
    for (int j = 2; j <= 1000000; j++) { 
     MyCallable<ValueBean> worker = new MyCallable<ValueBean>(); 
     worker.setBean(new ValueBean(j, 0)); 
     Future<ValueBean> f = executor.submit(worker); 
     list.add(f); 
    } 
    System.out.println(System.currentTimeMillis() - currTime); 

    int mostLen = 0; 
    int mostInt = 0; 
    for (Future<ValueBean> f : list) { 
     final int len = f.get().getLen(); 
     if (len > mostLen) { 
      mostLen = len; 
      mostInt = f.get().getNum(); 
     } 
    } 
    executor.shutdown(); 
    System.out.println(System.currentTimeMillis() - currTime); 
    System.out.println("Most len is " + mostLen + " for " + mostInt); 
} 

public class MyCallable<T> implements Callable<ValueBean> { 
    public ValueBean bean; 

    public void setBean(ValueBean bean) { 
     this.bean = bean; 
    } 

    public ValueBean call() throws Exception { 
     long i = bean.getNum(); 
     int len = 0; 
     while ((i = next(i)) != 1) { 
      len++; 
     } 
     return new ValueBean(bean.getNum(), len); 
    } 
} 

public class ValueBean { 
    int num; 
    int len; 

    public ValueBean(int num, int len) { 
     this.num = num; 
     this.len = len; 
    } 

    public int getNum() { 
     return num; 
    } 

    public int getLen() { 
     return len; 
    } 
} 

long next(long i) { 
    if (i % 2 == 0) { 
     return i/2; 
    } else { 
     return i * 3 + 1; 
    } 
} 

दुर्भाग्य से, बहु संस्करण में काम किया 5 बार धीमी:

public static void main(String[] args) { 
    int mostLen = 0; 
    int mostInt = 0; 
    long currTime = System.currentTimeMillis(); 
    for(int j=2; j<=1000000; j++) { 
     long i = j; 
     int len = 0; 
     while((i=next(i)) != 1) { 
      len++; 
     } 
     if(len > mostLen) { 
      mostLen = len; 
      mostInt = j; 
     } 
    } 
    System.out.println(System.currentTimeMillis() - currTime); 
    System.out.println("Most len is " + mostLen + " for " + mostInt); 
} 


static long next(long i) { 
    if(i%2==0) { 
     return i/2; 
    } else { 
     return i*3+1; 
    } 
} 

मेरे गलती शुरू करने की कोशिश करने के लिए बहु सूत्रण था 4 प्रोसेसर (कोर) पर सिंगल-थ्रेडेड की तुलना में।

तब मैं थोड़ा और कच्चे दृष्टिकोण की कोशिश की:

static int mostLen = 0; 
static int mostInt = 0; 

synchronized static void updateIfMore(int len, int intgr) { 
    if (len > mostLen) { 
     mostLen = len; 
     mostInt = intgr; 
    } 
} 

public static void main(String[] args) throws InterruptedException { 
    long currTime = System.currentTimeMillis(); 
    final int numProc = Runtime.getRuntime().availableProcessors(); 
    System.out.println("numProc = " + numProc); 
    ExecutorService executor = Executors.newFixedThreadPool(numProc); 
    for (int i = 2; i <= 1000000; i++) { 
     final int j = i; 
     executor.execute(new Runnable() { 
      public void run() { 
       long l = j; 
       int len = 0; 
       while ((l = next(l)) != 1) { 
        len++; 
       } 
       updateIfMore(len, j); 
      } 
     }); 
    } 
    executor.shutdown(); 
    executor.awaitTermination(30, TimeUnit.SECONDS); 
    System.out.println(System.currentTimeMillis() - currTime); 
    System.out.println("Most len is " + mostLen + " for " + mostInt); 
} 


static long next(long i) { 
    if (i % 2 == 0) { 
     return i/2; 
    } else { 
     return i * 3 + 1; 
    } 
} 

और यह बहुत तेजी से काम किया, लेकिन अभी भी यह एकल थ्रेड दृष्टिकोण की तुलना में धीमी थी।

मुझे आशा है कि ऐसा इसलिए नहीं है क्योंकि मैंने बहुप्रतिष्ठक करने के तरीके को खराब कर दिया है, बल्कि यह विशेष गणना/एल्गोरिदम समानांतर गणना के लिए उपयुक्त नहीं है। अगर मैं गणना बदलने के लिए इसे और अधिक प्रोसेसर के साथ विधि next की जगह गहन बनाने के लिए:

long next(long i) { 
    Random r = new Random(); 
    for(int j=0; j<10; j++) { 
     r.nextLong(); 
    } 
    if (i % 2 == 0) { 
     return i/2; 
    } else { 
     return i * 3 + 1; 
    } 
} 

दोनों बहु संस्करणों में दो बार के रूप में एक 4 कोर मशीन पर singlethreaded संस्करण की तुलना में तेजी से और अधिक से अधिक निष्पादित करने के लिए शुरू करते हैं।

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

क्या बुनियादी नियम तय करने में मदद मिलेगी कि यदि किसी विशेष गणना काफी गहन है इसे समानांतर में चलाने के द्वारा अनुकूलित किया जा सकता है (वास्तव में इसे लागू करने के प्रयास किए बिना प्रयास?)

+1

यह केवल प्रश्न से संबंधित है, लेकिन सवाल में एल्गोरिदम [कोलात्ज़ अनुमान] (http://en.wikipedia.org/wiki/Collatz_conjecture) से संबंधित है। यह geekdom धन्यवाद [यह] (http://xkcd.com/710/) और [यह] (http://store.xkcd.com/xkcd/#CollatzConjecture) में अधिक प्रसिद्ध है। –

+0

मैं * अत्यधिक * ब्रायन गोएट्ज़ द्वारा पुस्तक [जावा कंसुरेंसी इन प्रैक्टिस] (http://www.amazon.com/Java-Concurrency- अभ्यास- Brian-Goetz/dp/0321349601) की अनुशंसा करता हूं। –

उत्तर

2

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

यह समस्या खुद को मल्टीथ्रेडिंग के बिना अनुकूलित करने के लिए उधार देती है क्योंकि बाद के कुछ मूल्यों में तेजी से गणना की जा सकती है यदि आपके पिछले परिणाम पहले से ही हैं। उदाहरण के लिए, j == 4 लें। एक बार आंतरिक लूप के माध्यम से i == 2 उत्पन्न करता है, लेकिन आपने j == 2 दो पुनरावृत्तियों के लिए परिणाम की गणना की है, यदि आपने len का मान सहेजा है तो आप इसे लेन (4) = 1 + लेन (2) के रूप में गणना कर सकते हैं।

len के पहले गणना मूल्यों को संग्रहीत करने के लिए एक सरणी का उपयोग करना और next विधि में थोड़ा सा twiddling, आप कार्य> 50x तेज कर सकते हैं।

+0

हाँ यह 1000-बैच वाले मल्टीथ्रेड वाले से 8 गुना तेजी से चलता है! मुझे आश्चर्य है कि क्या मैं इसे एक –

+0

@OlegMikheev multithread कर सकता हूं यह संभव हो सकता है। मैं 'ConcurrentHashMap' में देखता हूं ताकि मैं लॉकिंग के बारे में चिंता किए बिना कैश का निर्माण कर सकूं। हालांकि मुझे लगता है कि सरणी कार्यान्वयन बहुत तेज है क्योंकि जैसे ही 'i एन/2 को संतुष्ट करना होगा। यह बहुप्रचारित समाधान में मदद करता है, लेकिन कैशिंग समाधान नहीं। इसके अलावा एक साधारण सरणी कैश सीमा ~ ~ 42,000,000 तक स्केल नहीं कर सकता है। –

2

"क्या प्रदर्शन संदर्भ स्विचिंग और थ्रेड निर्माण की लागत से अधिक प्रदर्शन होगा?"

यह एक बहुत ही ओएस, भाषा और हार्डवेयर, निर्भर लागत है; this question में जावा में लागत के बारे में कुछ चर्चा है, लेकिन कुछ संख्याएं और कुछ पॉइंटर्स हैं कि लागत की गणना कैसे करें।

आप सीपीयू गहन काम के लिए प्रति सीपीयू या कम एक थ्रेड भी चाहते हैं। पॉइंटर to a thread on how to work out that number के लिए डेविड हार्केनेस के लिए धन्यवाद।

+0

सीपीयू-भारी कार्यों के लिए एक थ्रेड प्रति सीपीयू के लिए +1, हालांकि आप आम तौर पर काम के लिए एक सीपीयू और समन्वय के लिए एक (मुख्य धागा) चाहते हैं। –

+1

इसके अलावा, [यह उत्तर] देखें (http://stackoverflow.com/a/1980858/285873) CPU कोर की संख्या और अन्य उपयोगी बिट्स की संख्या को कैसे ढूंढें। बैच का उपयोग करने के लिए –

4

मल्टीथ्रेडिंग को कुशलतापूर्वक कार्यान्वित करने की कुंजी यह सुनिश्चित करना है कि लागत बहुत अधिक न हो। कोई निश्चित नियम नहीं हैं क्योंकि वे आपके हार्डवेयर पर भारी निर्भर करते हैं।

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

आपने प्रत्येक प्रारंभिक मूल्य के लिए एक रननेबल बनाने का चयन किया है ताकि आप 1000000 रननेबल बना सकें। आपको संभवतः आपके बेहतर परिणाम मिलेंगे, प्रत्येक रननेबल 1000 प्रारंभ मानों का बैच करें। जिसका अर्थ है कि आपको केवल ओवरहेड को कम करने वाले 1000 रननेबल की आवश्यकता है।

+2

+1 1,000,000 कार्यों के रूप में बहुत कम भुगतान के साथ एक उच्च ओवरहेड होता है (धागे के कारण कुछ भी नहीं होने के कारण "खो उत्पादकता" को कम करता है)। –

1

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

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