2009-08-20 15 views
6

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

समस्या निम्न है: मेरे पास लगभग 10'000 पुनरावृत्तियों के लगभग 1000 लूप हैं। इन loops अनुक्रमिक रूप से निष्पादित किया जाना चाहिए, लेकिन मेरे पास 4 सीपीयू उपलब्ध हैं। मैं जो करने की कोशिश करता हूं वह 10'000 पुनरावृत्ति लूप को 4 2'500 पुनरावृत्तियों में विभाजित करना है, यानि प्रति थ्रेड। लेकिन मुझे अगले "बड़े" पुनरावृत्ति पर जाने से पहले 4 छोटे लूप खत्म होने की प्रतीक्षा करनी है। इसका मतलब है कि मैं नौकरियों को बंडल नहीं कर सकता।

मेरी समस्या यह है कि थ्रेड पूल और 4 धागे का उपयोग क्रमशः नौकरियों को करने से बहुत धीमा है (एक अलग थ्रेड द्वारा निष्पादित एक लूप को सीधे मुख्य धागे में इसे निष्पादित करने से बहुत धीमा है)।

मैं विंडोज़ पर हूं, इसलिए मैं CreateEvent() के साथ ईवेंट बनाता हूं और फिर WaitForMultipleObjects(2, handles, false, INFINITE) का उपयोग करके उनमें से एक पर प्रतीक्षा करता हूं जब तक कि मुख्य थ्रेड SetEvent() पर कॉल नहीं करता।

ऐसा प्रतीत होता है कि यह पूरी घटना चीज (महत्वपूर्ण वर्गों का उपयोग करके धागे के बीच सिंक्रनाइज़ेशन के साथ) बहुत महंगा है!

मेरा प्रश्न है: क्या यह सामान्य है कि घटनाओं का उपयोग करने से "बहुत समय" लगता है? यदि हां, तो क्या कोई और तंत्र है जिसका मैं उपयोग कर सकता हूं और यह कम समय-महंगा होगा?

// thread function 
unsigned __stdcall ThreadPool::threadFunction(void* params) { 
    // some housekeeping 
    HANDLE signals[2]; 
    signals[0] = waitSignal; 
    signals[1] = endSignal; 

    do { 
     // wait for one of the signals 
     waitResult = WaitForMultipleObjects(2, signals, false, INFINITE); 

     // try to get the next job parameters; 
     if (tp->getNextJob(threadId, data)) { 
      // execute job 
      void* output = jobFunc(data.params); 

      // tell thread pool that we're done and collect output 
      tp->collectOutput(data.ID, output); 
     } 

     tp->threadDone(threadId); 
    } 
    while (waitResult - WAIT_OBJECT_0 == 0); 

    // if we reach this point, endSignal was sent, so we are done ! 

    return 0; 
} 

// create all threads 
for (int i = 0; i < nbThreads; ++i) { 
    threadData data; 
    unsigned int threadId = 0; 
    char eventName[20]; 

    sprintf_s(eventName, 20, "WaitSignal_%d", i); 

    data.handle = (HANDLE) _beginthreadex(NULL, 0, ThreadPool::threadFunction, 
     this, CREATE_SUSPENDED, &threadId); 
    data.threadId = threadId; 
    data.busy = false; 
    data.waitSignal = CreateEvent(NULL, true, false, eventName); 

    this->threads[threadId] = data; 

    // start thread 
    ResumeThread(data.handle); 
} 

// add job 
void ThreadPool::addJob(int jobId, void* params) { 
    // housekeeping 
    EnterCriticalSection(&(this->mutex)); 

    // first, insert parameters in the list 
    this->jobs.push_back(job); 

    // then, find the first free thread and wake it 
    for (it = this->threads.begin(); it != this->threads.end(); ++it) { 
     thread = (threadData) it->second; 

     if (!thread.busy) { 
      this->threads[thread.threadId].busy = true; 

      ++(this->nbActiveThreads); 

      // wake thread such that it gets the next params and runs them 
      SetEvent(thread.waitSignal); 
      break; 
     } 
    } 

    LeaveCriticalSection(&(this->mutex)); 
} 
+0

अपने प्रश्न को सटीक बनाने के लिए संपादित करें ... – neuro

उत्तर

1

यदि आप लूप समानांतर हैं और बनाम 2008 का उपयोग कर रहे हैं, तो मैं ओपनएमपी को देखने का सुझाव दूंगा। यदि आप विजुअल स्टूडियो 2010 बीटा 1 का उपयोग कर रहे हैं, तो मैं parallel pattern library, विशेष रूप से "parallel for"/"parallel for each" apis या "task group कक्षा को देखने का सुझाव दे रहा हूं क्योंकि ये संभवतः कम कोड के साथ करने की कोशिश कर रहे हैं।

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

मैं इसे xperf जैसे प्रोफाइलर के तहत दृश्य स्टूडियो 2010 बीटा 1 में एफ 1 प्रोफाइलर के तहत देख रहा हूं (इसमें 2 नए समवर्ती मोड हैं जो विवाद देखने में मदद करते हैं) या इंटेल के vtune।

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

गुड लक

-रिक

+0

आपके उत्तर के लिए धन्यवाद। जब आप उपयोगी लिंक प्रदान करते हैं और ओपनएमपी के उपयोग का सुझाव देते हैं तो मैं आपका स्वीकार करूंगा! – Wookai

1

संदर्भ धागे के बीच स्विच भी महंगा हो सकता है:

यहाँ (कुछ प्रासंगिक मेरी थ्रेड पूल वर्ग से नकल भागों) वर्णन करने के लिए कुछ कोड है। कुछ मामलों में यह एक दिलचस्प ढांचा विकसित करना दिलचस्प है जिसका उपयोग आप अपनी नौकरी को अनुक्रमिक रूप से एक धागे या एकाधिक धागे के साथ संसाधित करने के लिए कर सकते हैं। इस तरह आप दो दुनिया के सर्वश्रेष्ठ हो सकते हैं।

वैसे, आपका प्रश्न वास्तव में क्या है? मैं एक और अधिक सटीक सवाल :) के साथ अधिक सटीक जवाब देने में सक्षम हो जाएगा

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

घटनाओं हिस्सा कुछ मामलों में अपने संसाधन से ज्यादा उपभोग कर सकते हैं, लेकिन यह महंगा नहीं होना चाहिए, जब तक कि आपके प्रसंस्करण वास्तव में है हासिल करने के लिए तेज़। इस मामले में, thredas के बीच स्विचिंग भी महंगी है, इसलिए मेरा जवाब अनुक्रमिक रूप से चीजों को करने पर पहला हिस्सा है ...

आपको इंटर-थ्रेड सिंक्रनाइज़ेशन बाधाओं को देखना चाहिए। आप के साथ ...

संपादित करें शुरू करने के लिए प्रतीक्षा समय धागे का पता लगाने कर सकते हैं: अधिक संकेत के बाद ...

मैं सही ढंग से लगता है, तो अपनी समस्या कुशलता से कुछ प्रसंस्करण essencialy parralellize करने के लिए सभी अपने कंप्यूटर कोर/प्रोसेसर का उपयोग करने के लिए है अनुक्रमिक।

लें कि आपके उदाहरण के रूप में गणना करने के लिए आपके पास 4 कोर और 10000 लूप हैं (एक टिप्पणी में)। आपने कहा कि आपको आगे बढ़ने से पहले 4 धागे खत्म होने की प्रतीक्षा करनी होगी। फिर आप अपनी सिंक्रनाइज़ेशन प्रक्रिया को सरल बना सकते हैं। आपको बस अपने चार धागे थ्रू एनएच, एनएच + 1, एनएच + 2, एनएच + 3 लूप देने की जरूरत है, चार धागे को पूरा करने के लिए प्रतीक्षा करें। आपको एक मिलनसार या बाधा का उपयोग करना चाहिए (एक सिंक्रनाइज़ेशन तंत्र जो एन धागे को पूरा करने की प्रतीक्षा करता है)। Boost में ऐसी व्यवस्था है। आप दक्षता के लिए विंडोज कार्यान्वयन देख सकते हैं। आपका धागा पूल वास्तव में कार्य के लिए उपयुक्त नहीं है। एक महत्वपूर्ण खंड में उपलब्ध थ्रेड की खोज आपके सीपीयू समय को मार रही है। घटना भाग नहीं है।

+0

एमएमएच, मुझे लगता है कि मेरा प्रश्न घटनाओं का उपयोग करने की लागत के बारे में है (क्या वे वास्तव में महंगी हैं या क्या मैं गलत काम कर रहा हूं?)। – Wookai

+0

हाँ, अपने प्रश्न को संपादित करें यह बेहतर होगा ... – neuro

+1

न्यूरो का दृष्टिकोण शायद आपकी सबसे अच्छी शर्त है। आपकी दूसरी पसंद है अपने लूप को फिर से डिजाइन करना ताकि वे एक-दूसरे पर भरोसा न करें, यदि आप कर सकते हैं। आपको एक पेर्फ पेनल्टी का भुगतान करना पड़ सकता है, लेकिन यह ठीक है: कोड जो x2 धीमा है लेकिन हार्डवेयर धागे की संख्या के साथ रैखिक रूप से स्केल समग्र रूप से जीतता है, है ना? –

1

यह महंगा नहीं होना चाहिए, लेकिन यदि आपका काम शायद ही कभी लेता है, तो थ्रेड और सिंक ऑब्जेक्ट्स का ओवरहेड महत्वपूर्ण हो जाएगा। इस तरह के थ्रेड पूल लंबे समय तक प्रसंस्करण नौकरियों के लिए या उन लोगों के लिए बेहतर हैं जो CPU संसाधनों के बजाय बहुत सारे आईओ का उपयोग करते हैं। यदि आप नौकरी संसाधित करते समय सीपीयू-बाध्य हैं, तो सुनिश्चित करें कि आपके पास प्रति सीपीयू केवल 1 थ्रेड है।

अन्य समस्याएं हो सकती हैं, GetNextJob को डेटा को संसाधित करने के लिए कैसे मिलता है? यदि डेटा कॉपी करने की बड़ी मात्रा है, तो आपने अपना ओवरहेड फिर से बढ़ाया है।

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

+0

मेरे पास वही ऑप्टिमाइज़ेशन विचार था, यानी थ्रेड्स को WaitForMultipleObjects() के माध्यम से बिना नौकरियों को खींचने देना, लेकिन मेरे मामले में मेरे पास प्रति थ्रेड बहुत कम नौकरियां हैं, इसलिए यह ज्यादा नहीं बदलेगा। – Wookai

+0

मैंने सोचा था कि आपके पास प्रति थ्रेड 2500 था? कोई बात नहीं - विकल्प ओपनएमपी की जांच करना है जो कि जल्दी से हो सकता है, और लागू करने के लिए निश्चित रूप से आसान हो सकता है। (यानी आप लूप के सामने सिर्फ एक प्रगति डालते हैं और इसे आपके लिए सबकुछ प्रबंधित करते हैं)। – gbjbaanb

3

हाँ, WaitForMultipleObjects बहुत महंगा है। यदि आपकी नौकरियां छोटी हैं, तो सिंक्रनाइज़ेशन ओवरहेड वास्तव में नौकरी करने की लागत को खत्म कर देगा, जैसा कि आप देख रहे हैं।

इसे ठीक करने का एक तरीका एक में कई नौकरियों को बंडल करना है: यदि आपको "छोटी" नौकरी मिलती है (हालांकि आप ऐसी चीजों का मूल्यांकन करते हैं), इसे तब तक स्टोर करें जब तक कि आपके पास एक उचित आकार की नौकरी बनाने के लिए पर्याप्त छोटी नौकरियां न हों। फिर उन सभी को प्रसंस्करण के लिए एक कार्यकर्ता थ्रेड में भेजें।

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

+0

समस्या निम्न है: मेरे पास लगभग 10'000 पुनरावृत्तियों के लगभग 1000 लूप हैं। इन loops अनुक्रमिक रूप से निष्पादित किया जाना चाहिए, लेकिन मेरे पास 4 सीपीयू उपलब्ध हैं। मैं जो करने की कोशिश करता हूं वह 10'000 पुनरावृत्ति लूप को 4 2'500 पुनरावृत्तियों में विभाजित करना है, यानि प्रति थ्रेड। लेकिन मुझे अगले "बड़े" पुनरावृत्ति पर जाने से पहले 4 छोटे लूप खत्म होने की प्रतीक्षा करनी है। इसका मतलब है कि मैं नौकरियों को बंडल नहीं कर सकता। – Wookai

+0

उस प्रश्न में रखें;) – neuro

+0

यह वास्तविक समस्या है ... मेरे 2 सेंट के लिए मेरे उत्तर में मेरा संपादन देखें ... – neuro

3

यह मुझे एक निर्माता उपभोक्ता पैटर्न के रूप में देखता है, जिसे दो सेफफोर्स के साथ प्रत्यारोपित किया जा सकता है, एक कतार ओवरफ्लो की रक्षा करता है, दूसरा खाली कतार।

आप कुछ विवरण here पा सकते हैं।

+0

क्या सेमेफोर घटनाओं से कम महंगे हैं? – Wookai

+0

इसका मतलब "महंगा" क्या है? संसाधनों के संदर्भ में? लॉक/अनलॉक करने के लिए कर्नेल समय के संदर्भ में? –

+0

मुझे नहीं लगता कि कोई अंतर है। वैसे भी, एक अंतर देखा जा सकता है। आप हमेशा एक प्रोफाइलर के साथ उपाय कर सकते हैं। –

2

देखें, अंत में सिग्नल उत्सर्जित होने के बाद भी आप अगले नौकरी के लिए पूछ रहे हैं।

for(;;) { 
    // wait for one of the signals 
    waitResult = WaitForMultipleObjects(2, signals, false, INFINITE); 
    if(waitResult - WAIT_OBJECT_0 != 0) 
     return; 
    //.... 
} 
+0

इसे इंगित करने के लिए धन्यवाद। यह कोई समस्या नहीं है क्योंकि जब जॉब सूची खाली होती है तो अंत सिग्नल को बुलाया जाता है, इसलिए उसे कोई नौकरी नहीं मिल जाएगी और सही तरीके से खत्म हो जाएगी। लेकिन तुम बिल्कुल सही हो! – Wookai

1

ऐसा लगता है कि इस पूरे घटना बात (महत्वपूर्ण वर्गों का उपयोग कर तुल्यकालन धागे के बीच के साथ) बहुत महंगा है!

"महंगा" एक सापेक्ष शब्द है। क्या जेट महंगे हैं? क्या कारें हैं या साइकिल ... जूते ...?

इस मामले में, सवाल यह है कि: जॉबफंक्शन के निष्पादन के लिए किए गए समय के सापेक्ष घटनाएं "महंगे" हैं? यह कुछ पूर्ण आंकड़े प्रकाशित करने में मदद करेगा: "अप्रचलित" होने पर प्रक्रिया कितनी देर तक लेती है? क्या यह महीनों, या कुछ femtoseconds है?

जब आप थ्रेडपूल आकार को बढ़ाते हैं तो उस समय क्या होता है? 1 के पूल आकार का प्रयास करें, फिर 2 फिर 4, इत्यादि।

इसके अलावा, जैसा कि आपको अतीत में थ्रेडपूल के साथ कुछ समस्याएं थीं, मैं कुछ थ्रेड का सुझाव दूंगा कि आपकी थ्रेडफंक्शन वास्तव में आह्वान किया जाता है ... क्या आप इसकी अपेक्षा से मेल खाते हैं?

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

1

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

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

+0

अंतर्दृष्टि के लिए धन्यवाद! ओपनएमपी ने थोड़ी मदद की, लेकिन इससे मुझे अपने कस्टम थ्रेड पूल से छुटकारा पाने में मदद मिली और कुछ और भरोसेमंद पर भरोसा किया। – Wookai

+0

ओपनएमपी शायद मदद की क्योंकि यह निष्पादन के लिए वर्तमान धागे का उपयोग करता है। इस प्रकार आपके मामले में 20% कम सिंक्रनाइज़ेशन है। इसके अलावा इसे अक्सर नींद से पहले एक छोटे स्पिन पाश के साथ कार्यान्वित किया जाता है, इसलिए यदि आपका निष्पादन तेजी से होता है, तो यह कई मामलों में घटनाओं को पूरी तरह से खत्म करने में मदद कर सकता है। – Juice

0

जैसा कि पहले उल्लेख किया गया है, थ्रेडिंग द्वारा जोड़ा गया ओवरहेड की मात्रा आपके द्वारा परिभाषित "नौकरियों" को करने के लिए किए गए समय की सापेक्ष मात्रा पर निर्भर करती है। तो काम के टुकड़ों के आकार में संतुलन ढूंढना महत्वपूर्ण है जो टुकड़ों की संख्या को कम करता है लेकिन प्रोसेसर को पूरा करने के लिए कंप्यूटेशंस के अंतिम समूह की प्रतीक्षा नहीं करता है।

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

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