2016-01-25 5 views
7

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

// Sorry for the long code 
#include <vector> 
#include <queue> 

#include <thread> 
#include <mutex> 
#include <future> 

#include "task.hpp" 

class ThreadPool 
{ 
public: 
    ThreadPool() 
    { 
     for (unsigned i = 0; i < std::thread::hardware_concurrency() - 1; i++) 
      m_workers.emplace_back(this, i); 

     m_running = true; 
     for (auto&& worker : m_workers) 
      worker.start(); 
    } 
    ~ThreadPool() 
    { 
     m_running = false; 
     m_task_signal.notify_all(); 
     for (auto&& worker : m_workers) 
      worker.terminate(); 
    } 

    void add_task(Task* task) 
    { 
     { 
      std::unique_lock<std::mutex> lock(m_in_mutex); 
      m_in.push(task); 
     } 
     m_task_signal.notify_one(); 
    } 
private: 
    class Worker 
    { 
    public: 
     Worker(ThreadPool* parent, unsigned id) : m_parent(parent), m_id(id) 
     {} 
     ~Worker() 
     { 
      terminate(); 
     } 

     void start() 
     { 
      m_thread = new std::thread(&Worker::work, this); 
     } 
     void terminate() 
     { 
      if (m_thread) 
      { 
       if (m_thread->joinable()) 
       { 
        m_thread->join(); 
        delete m_thread; 
        m_thread = nullptr; 
        m_parent = nullptr; 
       } 
      } 
     } 
    private: 
     void work() 
     { 
      while (m_parent->m_running) 
      {    
       std::unique_lock<std::mutex> lock(m_parent->m_in_mutex); 
       m_parent->m_task_signal.wait(lock, [&]() 
       { 
        return !m_parent->m_in.empty() || !m_parent->m_running; 
       }); 

       if (!m_parent->m_running) break; 
       Task* task = m_parent->m_in.front(); 
       m_parent->m_in.pop(); 
       // Fixed the mutex being locked while the task is executed 
       lock.unlock(); 

       task->execute();    
      } 
     } 
    private: 
     ThreadPool* m_parent = nullptr; 
     unsigned m_id = 0; 

     std::thread* m_thread = nullptr; 
    }; 
private: 
    std::vector<Worker> m_workers; 

    std::mutex m_in_mutex; 
    std::condition_variable m_task_signal; 
    std::queue<Task*> m_in; 

    bool m_running = false; 
}; 

class TestTask : public Task 
{ 
public: 
    TestTask() {} 
    TestTask(unsigned number) : m_number(number) {} 

    inline void Set(unsigned number) { m_number = number; } 

    void execute() override 
    { 
     if (m_number <= 3) 
     { 
      m_is_prime = m_number > 1; 
      return; 
     } 
     else if (m_number % 2 == 0 || m_number % 3 == 0) 
     { 
      m_is_prime = false; 
      return; 
     } 
     else 
     { 
      for (unsigned i = 5; i * i <= m_number; i += 6) 
      { 
       if (m_number % i == 0 || m_number % (i + 2) == 0) 
       { 
        m_is_prime = false; 
        return; 
       } 
      } 
      m_is_prime = true; 
      return; 
     } 
    } 
public: 
    unsigned m_number = 0; 
    bool m_is_prime = false; 
}; 

int main() 
{ 
    ThreadPool pool; 

    unsigned num_tasks = 1000000; 
    std::vector<TestTask> tasks(num_tasks); 
    for (auto&& task : tasks) 
     task.Set(randint(0, 1000000000)); 

    auto s = std::chrono::high_resolution_clock::now(); 
    #if MT 
    for (auto&& task : tasks) 
     pool.add_task(&task); 
    #else 
    for (auto&& task : tasks) 
     task.execute(); 
    #endif 
    auto e = std::chrono::high_resolution_clock::now(); 
    double seconds = std::chrono::duration_cast<std::chrono::nanoseconds>(e - s).count()/1000000000.0; 
} 

मानक:

10,000,000 tasks: 
    MT: 
     13 seconds of wall clock time 
     93.36% is spent in msvcp120.dll 
     3.45% is spent in Task::execute() // Not good here 
    ST: 
     0.5 seconds of wall clock time 
     97.31% is spent with Task::execute() 
+3

प्रारंभ:

यहाँ एक "लॉकसेल" कार्यान्वयन है। महत्वपूर्ण हो सकता है। – deviantfan

+0

@deviantfan मैंने उस गलती को बहुत देर तक पकड़ा। संशोधित उत्तर – Jarann

+0

कितने कोर? यदि केवल एक, बहुप्रचारित कोड आसानी से एकल से धीमा हो सकता है। –

उत्तर

6

इस तरह के सवालों के जवाब में सामान्य अस्वीकरण: एक ही रास्ता पक्का बताने के लिए एक प्रोफाइलर उपकरण के साथ यह मापने के लिए है।

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

अगला, आपके कार्य इतने सरल हैं कि एकल धागा उन्हें किसी भी समय निष्पादित नहीं करेगा। आप इस तरह के कार्यों के साथ किसी भी लाभ को माप नहीं सकते हैं। कुछ भारी कार्य करें जो कुछ और रोचक परिणाम उत्पन्न कर सकते हैं (कुछ कार्य जो असली दुनिया के करीब हैं, इस तरह के नहीं हैं)।

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

यूपीडी: मैंने कोड के गलत हिस्से को देखा। कार्य पर्याप्त जटिल है बशर्ते आप पर्याप्त रूप से बड़ी संख्या में कार्य करें।


UPD2: मैं अपने कोड के साथ खेला जाता है और कैसे मीट्रिक टन कोड बेहतर है को दिखाने के लिए एक अच्छा अभाज्य संख्या मिल गया है। निम्नलिखित प्राइम नंबर का उपयोग करें: 1019048297. यह अंतर दिखाने के लिए पर्याप्त गणना गणना प्रदान करेगा।

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

+0

क्या आपके पास कोई भारी कार्य है जो बनाने में बहुत मुश्किल नहीं होगा? – Jarann

+0

@ जेम्स, अद्यतन उत्तर देखें। मैंने आपके कोड – ixSci

+0

के गलत हिस्से को देखा है मैंने म्यूटेक्स समस्या तय की है और बेंचमार्क – Jarann

2

आप म्युटेक्स जबकि कार्य निष्पादित हो रही है पकड़ नहीं करना चाहिए, अन्यथा अन्य थ्रेड एक काम पाने के लिए सक्षम नहीं होगा:

void work() { 
    while (m_parent->m_running) { 
     Task* currentTask = nullptr;  
     std::unique_lock<std::mutex> lock(m_parent->m_in_mutex); 
     m_parent->m_task_signal.wait(lock, [&]() { 
      return !m_parent->m_in.empty() || !m_parent->m_running; 
     });      
     if (!m_parent->m_running) continue; 
     currentTask = m_parent->m_in.front(); 
     m_parent->m_in.pop();    
     lock.unlock(); //<- Release the lock so that other threads can get tasks 
     currentTask->execute(); 
     currentTask = nullptr; 
    } 
}  
+0

मैंने इसे ixSci के उत्तर – Jarann

+0

@ जेम्स से ठीक किया है, यह सुनने के लिए अच्छा है, बस उत्तर को स्वीकार करने के लिए सुनिश्चित करें कि इस मुद्दे को हल करने में आपकी सहायता हुई। –

1

मीट्रिक टन के लिए, कितना समय के प्रत्येक चरण में खर्च किया जाता है " ओवरहेड ": std::unique_lock, m_task_signal.wait, front, pop, unlock?

केवल 3% उपयोगी काम के आपके परिणामों के आधार पर, इसका मतलब है कि उपर्युक्त 97% उपभोग करता है। मुझे उपर्युक्त के प्रत्येक भाग के लिए संख्याएं मिलेंगी (उदाहरण के लिए प्रत्येक कॉल के बीच टाइमस्टैम्प जोड़ें)।

ऐसा लगता है कि आप जिस कोड का उपयोग करते हैं [केवल] अगले कार्य सूचक को हटाकर काफी भारी है। मैं एक बहुत ही सरल कतार [संभवतः लॉकलेस] तंत्र करूँगा। या, शायद, उपरोक्त पांच चरणों की प्रक्रिया के बजाय कतार में एक सूचकांक को टक्कर देने के लिए परमाणुओं का उपयोग करें। उदाहरण के लिए:

void 
work() 
{ 
    while (m_parent->m_running) { 
     // NOTE: this is just an example, not necessarily the real function 
     int curindex = atomic_increment(&global_index); 
     if (curindex >= max_index) 
      break; 

     Task *task = m_parent->m_in[curindex]; 

     task->execute(); 
    } 
} 

इसके अलावा, शायद आपको केवल एक के बजाय दस बार [कहना] पॉप करना चाहिए।

आप स्मृति बाध्य और/या "कार्य स्विच" बाध्य भी हो सकते हैं। (उदाहरण के लिए) एक सरणी तक पहुंचने वाले धागे के लिए, चार से अधिक धागे आमतौर पर मेमोरी बस को संतृप्त करते हैं। तुम भी, ताला के लिए भारी विवाद हो सकता है ऐसा है कि धागे भूखे हो, क्योंकि एक धागा [भी नई unlock कॉल के साथ, परोक्ष रूप से]

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

void 
work() 
{ 
    // assume m_id is 0,1,2,... 
    int curindex = m_id; 

    while (m_parent->m_running) { 
     if (curindex >= max_index) 
      break; 

     Task *task = m_parent->m_in[curindex]; 

     task->execute(); 

     curindex += NUMBER_OF_WORKERS; 
    } 
} 
+0

मेरे पास 4 (या किसी भी एकाधिक) कार्यों को धक्का देने और सभी 4 धागे को सूचित करने और कार्यों की एसेट राशि को पकड़ने के लिए एक ही विचार था। मैं थोड़े से परमाणुओं से परहेज कर रहा था क्योंकि मैंने अभी तक उन्हें पूरी तरह से नहीं सीखा है लेकिन वे पहली नज़र में मेरे सेटअप से बेहतर प्रतीत होते हैं। मै उसे करने की एक कोशिश तो करूंगा। – Jarann

+0

यहां एक लिंक है: http://stackoverflow.com/questions/33083270/atomically-increment-two-integers-with-cas इसमें एक सीएएस कार्यान्वयन है जिसे मैंने बनाया है।लेकिन, सबसे महत्वपूर्ण बात यह है कि इसमें सीपीपीकॉन में लॉकलेस के बारे में एक वीडियो टॉक के अंदर एक लिंक है –

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