2016-02-02 4 views
7

मेरे पास कुछ अंतराल धागे नियमित अंतराल (लगभग 1 किलोहाट) पर समय-महत्वपूर्ण प्रसंस्करण कर रहे हैं। प्रत्येक चक्र, श्रमिकों को एक कोर करने के लिए जागृत किया जाता है, जिनमें से प्रत्येक (औसत पर) अगले चक्र शुरू होने से पहले पूरा होना चाहिए। वे एक ही वस्तु पर काम करते हैं, जिसे कभी-कभी मुख्य धागे द्वारा संशोधित किया जा सकता है।दो परमाणुओं के साथ स्पिन-लॉक के लिए कम प्रतिबंधित मेमोरी ऑर्डरिंग

class Foo { 
public: 
    void Modify(); 
    void DoWork(SomeContext&); 
private: 
    std::atomic_flag locked = ATOMIC_FLAG_INIT; 
    std::atomic<int> workers_busy = 0; 
}; 

void Foo::Modify() 
{ 
    while(locked.test_and_set(std::memory_order_acquire)) ; // spin 
    while(workers_busy.load() != 0) ;       // spin 

    // Modifications happen here .... 

    locked.clear(std::memory_order_release); 
} 

void Foo::DoWork(SomeContext&) 
{ 
    while(locked.test_and_set(std::memory_order_acquire)) ; // spin 
    ++workers_busy; 
    locked.clear(std::memory_order_release); 

    // Processing happens here .... 

    --workers_busy; 
} 
:

दौड़ को रोकने के लिए है, लेकिन वस्तु अगले चक्र से पहले होने के संशोधन की अनुमति देने, मैं रिकॉर्ड करने के लिए कितने धागे अभी भी काम कर रहे एक परमाणु काउंटर के साथ एक स्पिन ताला का इस्तेमाल किया है

यह सभी शेष कार्यों को तुरंत पूरा करने की अनुमति देता है, बशर्ते कम से कम एक धागा शुरू हो गया हो, और अगले चक्र के लिए एक और कार्यकर्ता काम शुरू करने से पहले हमेशा ब्लॉक कर देगा।

atomic_flag को "अधिग्रहण" और "रिलीज" मेमोरी ऑर्डर के साथ एक्सेस किया गया है, जैसा कि सी ++ 11 के साथ स्पिन-लॉक को लागू करने का एक स्वीकार्य तरीका प्रतीत होता है। documentation at cppreference.com के अनुसार:

memory_order_acquire: इस स्मृति आदेश के साथ एक लोड आपरेशन प्रभावित स्मृति स्थान पर अधिग्रहण आपरेशन करता है: कोई स्मृति वर्तमान सूत्र में पहुँचता इस भार से पहले पुनर्क्रमित जा सकता है। यह सुनिश्चित करता है कि अन्य थ्रेड में सभी लिखते हैं जो समान परमाणु चर को छोड़ते हैं, वर्तमान धागे में दिखाई देते हैं।

memory_order_release: इस स्मृति आदेश के साथ एक दुकान संचालन रिहाई आपरेशन करता है: कोई स्मृति वर्तमान सूत्र में पहुँचता इस स्टोर के बाद पुनर्क्रमित जा सकता है। यह सुनिश्चित करता है कि वर्तमान धागे में सभी लिखने वाले अन्य धागे में दिखाई दे रहे हैं जो समान परमाणु चर प्राप्त करते हैं और लिखते हैं कि परमाणु चर में निर्भरता अन्य थ्रेडों में दिखाई देती है जो समान परमाणु का उपभोग करते हैं।

जैसा कि मैं उपरोक्त को समझता हूं, यह स्मृति क्रम के बारे में अत्यधिक रूढ़िवादी होने के बिना, म्यूटेक्स व्यवहार प्रदान करने के लिए धागे में संरक्षित पहुंच को सिंक्रनाइज़ करने के लिए पर्याप्त है।

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

++workers_busy पर कॉल, --workers_busy और workers_busy.load() वर्तमान में सभी में डिफ़ॉल्ट मेमोरी ऑर्डर, memory_order_seq_cst है। यह देखते हुए कि इस परमाणु के लिए एकमात्र दिलचस्प उपयोग Modify() को --workers_busy (जो स्पिन-लॉक म्यूटेक्स द्वारा सिंक्रनाइज़ नहीं किया गया है) को अनवरोधित करना है, क्या "अधिग्रहण" वृद्धि का उपयोग करके समान चर-रिलीज मेमोरी ऑर्डर का उपयोग इस चर के साथ किया जा सकता है ? अर्थात

void Foo::Modify() 
{ 
    while(locked.test_and_set(std::memory_order_acquire)) ; 
    while(workers_busy.load(std::memory_order_acquire) != 0) ; // <-- 
    // .... 
    locked.clear(std::memory_order_release); 
} 

void Foo::DoWork(SomeContext&) 
{ 
    while(locked.test_and_set(std::memory_order_acquire)) ; 
    workers_busy.fetch_add(1, std::memory_order_relaxed);   // <-- 
    locked.clear(std::memory_order_release); 
    // .... 
    workers_busy.fetch_sub(1, std::memory_order_release);   // <-- 
} 

यह सही है? क्या इनमें से किसी भी मेमोरी ऑर्डरिंग को आराम से आराम करना संभव है? और क्या इससे कोई फर्क पड़ता है?

+0

अस्वीकरण: परमाणुओं पर एक विशेषज्ञ नहीं। क्या स्पिन लॉक के बाहर 'fetch_sub' कम से कम' memory_order_acq_rel' होना चाहिए ताकि यह सुनिश्चित किया जा सके कि यह अन्य धागे द्वारा गिनती पर लिखता है, _and_ कि अन्य धागे इसे लिखते हुए लिखते हैं? कुछ दिख रहा है। – ShadowRanger

+1

आपका हार्डवेयर प्लेटफ़ॉर्म क्या है? ध्यान रखें कि सी ++ की कुछ मेमोरी ऑर्डरिंग सुविधाएं अपेक्षाकृत गूढ़ प्लेटफार्मों के लिए हैं। आप बिना किसी प्रत्यक्ष लाभ के लिए बहुत सारे काम और सीख रहे हैं! – Yakk

+1

@Yakk: सिर्फ इसलिए कि कुछ 'memory_order' स्थिरांक द्वारा कोई विशेष हार्डवेयर विशेषताएं नहीं हैं इसका मतलब यह नहीं है कि वे कुछ भी नहीं करते हैं। यहां तक ​​कि x86 पर (जिसने मेमोरी सेमेन्टिक्स को दृढ़ता से आदेश दिया है), 'memory_order' की पसंद संकलक अनुकूलन/पुनर्निर्देशन प्रतिबंधों को बदलती है; एक पंक्ति में दो 'memory_order_relaxed' ऑपरेशंस को कंपाइलर द्वारा स्वैप किया जा सकता है, इसलिए दूसरा ऑपरेशन पहले होता है। इसी प्रकार, 'आराम' या 'रिलीज' स्टोर की आम तौर पर शून्य ओवरहेड होती है, लेकिन 'memory_order_seq_cst' के साथ, कंपाइलर स्पष्ट, महंगा (~ 100 चक्र देरी)' mfence' निर्देश जोड़ता है। – ShadowRanger

उत्तर

3

Since you say you're targeting x86 only, आप guaranteed strongly-ordered memory anyway हैं; memory_order_seq_cst से बचने के लिए उपयोगी है (यह महंगा और अनावश्यक मेमोरी बाड़ को ट्रिगर कर सकता है), लेकिन उससे परे, अधिकांश अन्य ऑपरेशंस किसी विशेष ओवरहेड को लागू नहीं करेंगे, इसलिए आपको संभवतः गलत कंपाइलर निर्देश रीडरिंग की इजाजत देने के अलावा अतिरिक्त छूट से कुछ भी हासिल नहीं होगा। यह सुरक्षित होना चाहिए, और किसी भी अन्य समाधान की तुलना में कोई धीमी सी ++ 11 एटोमिक्स का उपयोग कर:

void Foo::Modify() 
{ 
    while(locked.test_and_set(std::memory_order_acquire)) ; 
    while(workers_busy.load(std::memory_order_acquire) != 0) ; // acq to see decrements 
    // .... 
    locked.clear(std::memory_order_release); 
} 

void Foo::DoWork(SomeContext&) 
{ 
    while(locked.test_and_set(std::memory_order_acquire)) ; 
    workers_busy.fetch_add(1, std::memory_order_relaxed); // Lock provides acq and rel free 
    locked.clear(std::memory_order_release); 
    // .... 
    workers_busy.fetch_sub(1, std::memory_order_acq_rel); // No lock wrapping; acq_rel 
} 

पर सबसे बुरा, 86 पर, यह कुछ संकलक आदेश की कमी लगाता है; इसे अतिरिक्त बाड़ या लॉक निर्देशों को लॉन्च नहीं करना चाहिए जिन्हें लॉक करने की आवश्यकता नहीं है।

-4

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

unsigned int volatile lock_var = 0; 
#define ACQUIRE_LOCK() {                   
        do {                  
         while(lock_var == 1) {            
          _mm_pause;              
         }                 
        } while(__sync_val_compare_and_swap(&lock_var, 0, 1) == 1);    
       } 
#define RELEASE_LOCK() lock_var = 0 
// 

इंटेल द्वारा प्रोसेसर के लिए _mm_pause की सिफारिश की जाती है, इसलिए लॉक को अपडेट करने का समय होता है।

आपका धागा केवल लॉक प्राप्त करते समय लूप के दौरान बाहर निकल जाएगा और फिर महत्वपूर्ण अनुभाग दर्ज करेगा।

यदि आप __sync_val_compare_and_swap के लिए प्रलेखन को देखते हैं तो आपको पता चलेगा कि यह xchgcmp निर्देश के आधार पर है और इस निर्देश के दौरान बस को लॉक करने के लिए उत्पन्न असेंबली में इसके ऊपर शब्द लॉक होगा। यह guarentees एक परमाणु पढ़ने लिखने में संशोधन।

+0

क्या आपके पास "बड़ा अंतर बनाता है" के पीछे संख्याएं हैं? आपके द्वारा निर्दिष्ट डिज़ाइन अधिग्रहण/रिलीज सेमेन्टिक्स के साथ परमाणुओं के सही उपयोग से कुछ हद तक तेज हो सकता है, लेकिन यह संकलक पर किसी ऑर्डरिंग बाधाओं को भी लागू नहीं कर रहा है; 'RELEASE_LOCK' की कार्रवाई को कम से कम पुन: व्यवस्थित किया जा सकता है (संकलक द्वारा, हालांकि x86 पर, सीपीयू मजबूत आदेश गारंटी के लिए धन्यवाद नहीं) जो लॉक द्वारा संरक्षित किए जाने वाले उत्परिवर्तन से पहले होता है, इसलिए आप नहीं करेंगे उन दौड़ स्थितियों से बचें जिन्हें आप रोकने की कोशिश कर रहे हैं। यह गैर-इंटेल (संभावित गैर-x86) आर्किटेक्चर के लिए भी पोर्टेबल है। – ShadowRanger

+0

हाँ मैं '_mm_pause' के बारे में जानता हूं, और पहले से ही इसका उपयोग कर रहा हूं। मैंने इसे अपने प्रश्न से बाहर कर दिया क्योंकि यह प्रासंगिक प्रतीत नहीं हुआ था। मेरे ज्ञान के लिए, 'std :: atomic_flag' बिल्कुल उसी' xchgcmp' निर्देश में अनुवाद करेगा। हालांकि, मुझे विश्वास नहीं है कि परमाणु स्पष्ट करने के बजाय मूल्य को शून्य पर सेट करना परमाणु होने का अंत होगा, न ही यह अनुमानित स्मृति आदेश होगा। – paddy

+0

हाँ यह मेरे स्वामी थीसिस विषय का एक हिस्सा है और यह प्रदर्शन के लिए लगभग 10% अंतर बनाता है। यदि आप ऑर्डर करना चाहते हैं तो आपको बेकरी लॉक की तरह कुछ देखने की ज़रूरत है जो कि जब थ्रेड लॉक प्राप्त करने का प्रयास करता है तो उस पर आधारित होगा।ब्लैक एंड व्हाइट बेकरी या एमसीएस लॉक भी ऑर्डर प्रदान करेंगे लेकिन –

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