मैं एक ही बात जानना चाहता था, इसलिए मैंने इसे मापा। मेरे बॉक्स (AMD FX (tm) -8150 E-Core Processor 3.612361 GHz पर), एक अनलॉक किए गए म्यूटेक्स को लॉक और अनलॉक कर रहा है जो कि अपनी कैश लाइन में है और पहले से ही कैश है, 47 घड़ियां (13 एनएस) लेता है।
दो कोर के बीच सिंक्रनाइज़ेशन के कारण (मैंने CPU # 0 और # 1 का उपयोग किया), मैं केवल एक थ्रेड / अनलॉक जोड़ी को दो थ्रेड्स पर हर 102 ns पर कॉल कर सकता था, इसलिए एक बार हर 51 ns, जिसमें से कोई निष्कर्ष निकाल सकता है कि यह लगभग 38 लेता है ns अगले धागे को फिर से लॉक करने से पहले एक थ्रेड को अनलॉक करने के बाद पुनर्प्राप्त करने के लिए।
जिस कार्यक्रम की मैं इसकी जाँच करता था, वह यहाँ पाया जा सकता है:
https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39a41809d3de2e9e4b8/src/mutex_test.cxx.cxx
ध्यान दें कि इसमें कुछ हार्डकोड मान हैं जो मेरे बॉक्स के लिए विशिष्ट हैं (xrange, yrange और rdtsc ओवरहेड), इसलिए आपको संभवतः आपके साथ काम करने से पहले इसके साथ प्रयोग करना होगा।
यह उस स्थिति में पैदा होने वाला ग्राफ है:
यह निम्नलिखित कोड पर बेंचमार्क रन का परिणाम दिखाता है:
uint64_t do_Ndec(int thread, int loop_count)
{
uint64_t start;
uint64_t end;
int __d0;
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (start) : : "%rdx");
mutex.lock();
mutex.unlock();
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (end) : : "%rdx");
asm volatile ("\n1:\n\tdecl %%ecx\n\tjnz 1b" : "=c" (__d0) : "c" (loop_count - thread) : "cc");
return end - start;
}
दो rdtsc कॉल घड़ियों की संख्या को मापती हैं, जिन्हें `mutex 'लॉक करना और अनलॉक करना है (मेरे बॉक्स पर rdtsc कॉल के लिए 39 घड़ियों का ओवरहेड)। तीसरा एएसएम एक विलंब लूप है। विलंब लूप का आकार थ्रेड 1 के लिए 1 काउंट छोटा होता है, यह थ्रेड 0 के लिए होता है, इसलिए थ्रेड 1 थोड़ा तेज होता है।
उपरोक्त फ़ंक्शन को 100,000 के तंग लूप में कहा जाता है। इसके बावजूद कि फ़ंक्शन थ्रेड 1 के लिए थोड़ा तेज़ है, दोनों लूप्स म्यूटेक्स को कॉल के कारण सिंक्रनाइज़ करते हैं। यह इस तथ्य से ग्राफ में दिखाई देता है कि लॉक / अनलॉक जोड़ी के लिए मापी गई घड़ियों की संख्या थ्रेड 1 के लिए थोड़ी बड़ी है, इसके नीचे लूप में कम देरी के लिए जिम्मेदार है।
ऊपर के ग्राफ में नीचे दायां बिंदु 150 की देरी लूप_काउंट के साथ एक माप है, और फिर नीचे की ओर बिंदुओं का अनुसरण करते हुए, बाईं ओर, लूप_काउंट प्रत्येक माप द्वारा कम किया जाता है। जब यह 77 हो जाता है तो दोनों थ्रेड्स में हर 102 ns फ़ंक्शन को कहा जाता है। यदि बाद में loop_count कम हो जाता है, तो भी थ्रेड्स को सिंक्रनाइज़ करना संभव नहीं होता है और म्यूटेक्स वास्तव में अधिकांश समय लॉक होना शुरू हो जाता है, जिसके परिणामस्वरूप घड़ियों की एक बढ़ी हुई मात्रा होती है जो लॉक / अनलॉक करने के लिए होती है। साथ ही फ़ंक्शन कॉल का औसत समय इस वजह से बढ़ जाता है; इसलिए भूखंड अंक अब ऊपर और दाईं ओर फिर जाते हैं।
इससे हम यह निष्कर्ष निकाल सकते हैं कि प्रत्येक 50 ns पर एक म्यूटेक्स को लॉक करना और अनलॉक करना मेरे बॉक्स पर कोई समस्या नहीं है।
मेरे सभी निष्कर्षों में यह है कि ओपी के सवाल का जवाब यह है कि अधिक म्यूटेक्स जोड़ना बेहतर है क्योंकि इससे कम विवाद होता है।
जितना हो सके म्यूटेक्स को लॉक करने की कोशिश करें। उन्हें लगाने का एकमात्र कारण -say- एक लूप के बाहर होगा यदि वह लूप हर 100 ns (या बल्कि, उन थ्रेड्स की संख्या जो एक ही समय 50 ns पर उस लूप को चलाना चाहते हैं) की तुलना में या 13 ns से अधिक तेज़ी से होता है लूप का आकार विवाद से आपको मिलने वाली देरी से अधिक देरी है।
संपादित करें: मुझे अब इस विषय पर बहुत अधिक ज्ञान हो गया है और इस निष्कर्ष पर संदेह करना शुरू कर देता हूं कि मैंने यहां प्रस्तुत किया था। सबसे पहले, सीपीयू 0 और 1 हाइपर-थ्रेडेड हो जाते हैं; भले ही AMD 8 असली कोर होने का दावा करता है, लेकिन निश्चित रूप से कुछ बहुत ही गड़बड़ है क्योंकि दो अन्य कोर के बीच देरी बहुत बड़ी है (यानी, 0 और 1 एक जोड़ी बनाते हैं, जैसा कि 2 और 3, 4 और 5, और 6 और 7 करते हैं। )। दूसरे, std :: mutex को इस तरह से लागू किया जाता है कि यह वास्तव में सिस्टम कॉल करने से पहले थोड़ी देर के लिए लॉक कर देता है जब यह म्यूटेक्स पर लॉक को तुरंत प्राप्त करने में विफल रहता है (जिसमें कोई संदेह नहीं है कि यह बहुत धीमी गति से होगा)। तो जो मैंने यहां मापा है वह पूर्ण रूप से आदर्श आदर्श है और लॉकिंग या अनलॉकिंग में प्रति लॉक या अनलॉक में अत्यधिक समय लग सकता है।
निचला रेखा, एटमिक्स के साथ एक म्यूटेक्स लागू किया जाता है। कोर के बीच परमाणुओं को सिंक्रनाइज़ करने के लिए एक आंतरिक बस को बंद करना होगा जो कई सौ घड़ी चक्रों के लिए इसी कैश लाइन को जमा देता है। इस मामले में कि ताला प्राप्त नहीं किया जा सकता है, थ्रेड को सोने के लिए रखने के लिए एक सिस्टम कॉल किया जाना है; यह स्पष्ट रूप से अत्यंत धीमा है (सिस्टम कॉल 10 mircoseconds के क्रम में हैं)। आम तौर पर यह वास्तव में कोई समस्या नहीं है क्योंकि उस धागे को वैसे भी सोना पड़ता है - लेकिन यह उच्च विवाद के साथ एक समस्या हो सकती है जहां एक धागा उस समय के लिए लॉक प्राप्त नहीं कर सकता है जो सामान्य रूप से घूमता है और इसलिए सिस्टम कॉल करता है, लेकिन कर सकता है इसके कुछ समय बाद वहां ताला लगा दें। उदाहरण के लिए, यदि कई थ्रेड्स लॉक करते हैं और एक टाइट लूप में म्यूटेक्स को अनलॉक करते हैं और प्रत्येक 1 माइक्रोसेकंड या इसके लिए लॉक को रखता है, और तब उन्हें इस तथ्य से काफी धीमा कर दिया जा सकता है कि उन्हें लगातार सोने के लिए रखा जाता है और फिर से जगाया जाता है। इसके अलावा, एक बार एक थ्रेड सो जाता है और दूसरे थ्रेड को जागना पड़ता है, उस थ्रेड को एक सिस्टम कॉल करना पड़ता है और ~ 10 माइक्रोसेकंड में देरी हो जाती है; यह देरी इस प्रकार म्यूटेक्स को अनलॉक करते समय होती है जब एक और धागा कर्नेल में म्यूटेक्स की प्रतीक्षा कर रहा होता है (स्पिनिंग के बाद बहुत लंबा समय लगता है)।