2012-01-05 13 views
20

std :: atomic C++ 11 द्वारा पेश की गई नई सुविधा का उपयोग कैसे करें, लेकिन मुझे इसका सही तरीके से उपयोग करने के तरीके पर अधिक ट्यूटोरियल नहीं मिल रहा है। तो क्या निम्न अभ्यास सामान्य और कुशल हैं?std :: atomic कुशलतापूर्वक

एक अभ्यास मैं प्रयोग किया जाता है कि हम एक बफर है और मैं कुछ बाइट्स पर कैस करना चाहते हैं, तो क्या मैंने किया था:

uint8_t *buf = .... 
auto ptr = reinterpret_cast<std::atomic<uint8_t>*>(&buf[index]); 
uint8_t oldValue, newValue; 
do { 
    oldValue = ptr->load(); 
    // Do some computation and calculate the newValue; 
    newValue = f(oldValue); 
} while (!ptr->compare_exchange_strong(oldValue, newValue)); 

तो मेरी प्रश्न हैं:

  1. उपरोक्त कोड का उपयोग करता है बदसूरत reinterpret_cast और यह & buf [index] स्थान के संदर्भ में परमाणु सूचक को पुनर्प्राप्त करने का सही तरीका है?
  2. क्या सीएएस एक मशीन शब्द पर सीएएस की तुलना में काफी धीमी है, तो मुझे इसका उपयोग करने से बचना चाहिए? मेरा कोड अधिक जटिल दिखाई देगा यदि मैं इसे एक शब्द लोड करने, बाइट निकालने, गणना करने और बाइट को नए मान में सेट करने के लिए बदलता हूं, और सीएएस करता हूं। इससे कोड अधिक जटिल हो जाता है और मुझे खुद को एड्रेस संरेखण से निपटने की भी आवश्यकता होती है।

संपादित करें: यदि वे प्रश्न प्रोसेसर/आर्किटेक्चर निर्भर हैं, तो x86/x64 प्रोसेसर के लिए निष्कर्ष क्या है?

+1

सी ++ कार्रवाई में Concurrency [(प्रारंभिक पहुंच)] (http://www.manning.com/williams/), [(अमेज़ॅन)] (http://www.amazon.com/gp/product/1933988770/ रेफरी = as_li_qf_sp_asin_tl?यानी = यूटीएफ 8 और टैग = गुमाडून -20 और लिंककोड = एएस 2 और शिविर = 1789 और रचनात्मक = 9 325 और क्रिएटिवएएसआईएन = 1 9 33 9 88770) शायद इस विषय पर शायद सबसे अच्छी किताब है, या बल्कि, होगी। – Cubbi

+1

परमाणुओं पर कई ट्यूटोरियल नहीं हैं क्योंकि, परमाणु झंडे जैसे कुछ साधारण मामलों के अलावा, यह एक खनन क्षेत्र है। "द हर्ट लॉकर" देखना परमाणुओं का उपयोग करने के लिए एक शर्त होना चाहिए। ताले का प्रयोग करें! –

उत्तर

23
  1. reinterpret_cast अपरिभाषित व्यवहार उत्पन्न करेगा। आपका चर या तो std::atomic<uint8_t> या सादा uint8_t है; आप उनके बीच नहीं डाले जा सकते हैं। उदाहरण के लिए आकार और संरेखण आवश्यकताओं अलग हो सकती हैं। जैसे कुछ प्लेटफ़ॉर्म केवल शब्दों पर परमाणु संचालन प्रदान करते हैं, इसलिए std::atomic<uint8_t> एक पूर्ण मशीन शब्द का उपयोग करेगा जहां सादा uint8_t केवल बाइट का उपयोग कर सकता है। गैर-परमाणु संचालन को सभी प्रकार के तरीकों से भी अनुकूलित किया जा सकता है, जिसमें आसपास के परिचालनों के साथ महत्वपूर्ण रूप से पुन: व्यवस्थित किया जा रहा है, और आसन्न स्मृति स्थानों पर अन्य परिचालनों के साथ मिलकर जहां प्रदर्शन में सुधार हो सकता है।

    इसका मतलब यह है कि यदि आप कुछ डेटा पर परमाणु संचालन चाहते हैं तो आपको पहले से ही पता होना चाहिए, और एक सामान्य बफर आवंटित करने के बजाय उपयुक्त std::atomic<> ऑब्जेक्ट्स बनाना होगा। बेशक, आप एक बफर आवंटित कर सकते हैं और उसके बाद उस बफर में अपने परमाणु चर को प्रारंभ करने के लिए प्लेसमेंट new का उपयोग कर सकते हैं, लेकिन आपको यह सुनिश्चित करना होगा कि आकार और संरेखण सही थे, और आप गैर-परमाणु संचालन का उपयोग करने में सक्षम नहीं होंगे वह वस्तु

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

  2. तो कैस एक शब्द की तुलना में एक बाइट के लिए धीमी है, तो आप std::atomic<unsigned> का उपयोग कर बंद बेहतर हो सकता है, लेकिन यह एक अंतरिक्ष जुर्माना होगा, और आप निश्चित रूप से सिर्फ कच्चे बाइट्स की एक दृश्य का उपयोग करने के std::atomic<unsigned> उपयोग नहीं कर सकते --- उस डेटा पर सभी ऑपरेशन उसी std::atomic<unsigned> ऑब्जेक्ट के माध्यम से होना चाहिए। आप आम तौर पर लिखने वाले कोड से बेहतर होते हैं जो आपको चाहिए और संकलक को ऐसा करने का सबसे अच्छा तरीका बताते हैं।

86/64 के लिए, के साथ एक std::atomic<unsigned> चर a, a.load(std::memory_order_acquire) और a.store(new_value,std::memory_order_release) कोई जहाँ तक वास्तविक निर्देश जाना गैर-परमाणु चर के भार और दुकानों से ज्यादा महंगे हैं, लेकिन वे संकलक अनुकूलन सीमित करते हैं। यदि आप डिफ़ॉल्ट std::memory_order_seq_cst का उपयोग करते हैं तो इनमें से एक या दोनों ऑपरेशंस LOCK एड निर्देश या बाड़ की सिंक्रनाइज़ेशन लागत लगेंगे (my implementation स्टोर पर मूल्य डालता है, लेकिन अन्य कार्यान्वयन अलग-अलग चुन सकते हैं)। हालांकि, memory_order_seq_cst ऑपरेशन "एकल कुल आदेश" बाधा के कारण कारणों के कारण आसान है।

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

2

आपका reinterpret_cast<std::atomic<uint8_t>*>(...) सबसे निश्चित रूप से एक परमाणु को पुनर्प्राप्त करने का सही तरीका नहीं है और काम करने के लिए भी गारंटी नहीं है। ऐसा इसलिए है क्योंकि std::atomic<T> को T के समान आकार की गारंटी नहीं है।

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

जो मैं देखता हूं उससे मौजूदा मूल्य पर std::atomic प्राप्त करने का कोई तरीका नहीं है, खासकर जब से वे एक ही आकार के लिए गारंटी नहीं देते हैं। इसलिए आपको वास्तव में bufstd::atomic<uint8_t>* बनाना चाहिए। इसके अलावा मुझे अपेक्षाकृत यकीन है कि भले ही ऐसी कास्ट काम करेगी, उसी पते पर गैर परमाणुओं के माध्यम से पहुंच की उम्मीद के रूप में काम करने की गारंटी नहीं दी जाएगी (क्योंकि इस पहुंच को बाइट्स के लिए भी परमाणु होने की गारंटी नहीं है)। तो एक स्मृति स्थान तक पहुंचने के लिए nonatomic साधन होने पर आप परमाणु संचालन करना चाहते हैं वास्तव में समझ में नहीं आता है।

ध्यान दें कि सामान्य आर्किटेक्चर स्टोर्स और बाइट्स के लोड के लिए परमाणु हैं, इसलिए जब तक आप उन परिचालनों के लिए आराम से स्मृति आदेश का उपयोग करते हैं, तब तक आपके पर परमाणुओं का उपयोग करने के लिए कोई प्रदर्शन ओवरहेड नहीं होता है। इसलिए यदि आप वास्तव में एक बिंदु पर निष्पादन के आदेश की परवाह नहीं करते हैं (उदा। क्योंकि प्रोग्राम अभी तक बहुप्रचारित नहीं है) a.store(0) के बजाय बस a.store(0, std::memory_order_relaxed) का उपयोग करें।

बेशक

अगर आप केवल 86 के बारे में अपने reinterpret_cast काम करने की संभावना है, लेकिन अपने प्रदर्शन सवाल शायद अभी भी निर्भर प्रोसेसर है बात कर रहे हैं (मुझे लगता है, मैं cmpxchg के लिए वास्तविक अनुदेश समय देखा नहीं किया है)।

+0

मैं 90% यकीन है कि बाइट पर परमाणु एक शब्द से धीमा हो जाएगा, क्योंकि इसे कुछ बिटवाई ऑपरेशन करने की आवश्यकता है। मैं जानना चाहता हूं कि यह कितना धीमा दिखता है। एक और बात यह है कि, मैं आपसे सहमत नहीं हूं कि एक एकल बाइट पढ़ना/लिखना परमाणु है, कम से कम x86 पर। आपके सुझाव के लिए धन्यवाद जो बाइट सरणी के बजाय परमाणु सरणी का उपयोग करता है, जो काम करता है, लेकिन बाइट्स धीमे से लोडिंग का कारण बनता है, जो मैं नहीं चाहता हूं। असल में 99% समय में, मैं बता सकता हूं कि कोई अन्य धागा सरणी में संग्रहित नहीं हो रहा है, इसलिए अतिरिक्त बाधा की आवश्यकता नहीं है। केवल थोड़े समय के लिए मुझे उपरोक्त सामान करने की आवश्यकता है। –

+0

@icando: जैसा कि मैंने कहा था कि इसका मंच निर्भर है। लेकिन चूंकि आप x86 के बारे में बात कर रहे हैं: एक शब्द पर एक बाइट पर एक परमाणु ऑपरेशन धीमा क्यों होगा? इसका मतलब क्या है कि इसे कुछ बिटवाई ऑपरेशंस करने की ज़रूरत है? X86 मूल रूप से बाइट्स स्टोर कर सकता है और इसमें 8 बिट 'cmpxchg' है, इसलिए इससे कोई फर्क नहीं पड़ता है (ठीक है कि बिल्कुल ठीक नहीं है, लेकिन इसके बाद और अधिक प्रभाव नहीं होना चाहिए, इसके बाद मशीनों के बजाए बाइट्स का उपयोग करना चाहिए)। और अतिरिक्त बाधा के बारे में: यही कारण है कि मैंने 'memory_order_relaxed' का सुझाव दिया, जो कि अधिकतर अतिरिक्त लागतों को खत्म कर देना चाहिए, क्योंकि लोड/स्टोर परमाणु है (कम से कम x86 पर)। – Grizzly

4

आपका कोड निश्चित रूप से गलत है और कुछ मजाकिया करने के लिए बाध्य है। अगर चीजें वास्तव में खराब होती हैं तो ऐसा हो सकता है जो आपको लगता है कि ऐसा करना है। मैं समझ नहीं पा रहा हूं कि कैसे ठीक से उपयोग करना है उदा।

std::atomic<uint8_t> value(0); 
uint8_t oldvalue, newvalue; 
do 
{ 
    oldvalue = value.load(); 
    newvalue = f(oldvalue); 
} 
while (!value.compare_exchange_strong(oldvalue, newvalue)); 

अब तक मेरी निजी नीति इस ताला मुक्त सामान में से किसी से दूर रहने और लोगों को पता है, जो वे क्या कर रहे करने के लिए इसे छोड़ने के लिए है: कैस लेकिन आप std::atomic<T> कुछ इस तरह का प्रयोग करेंगे। मैं atomic_flag और संभवतः काउंटर का उपयोग करूंगा और यह लगभग उतना ही है जितना मैं जाऊंगा। संकल्पनात्मक रूप से मैं समझता हूं कि यह लॉक-फ्री सामान कैसे काम करता है लेकिन मैं यह भी समझता हूं कि बहुत सी चीजें हैं जो गलत हो सकती हैं यदि आप बेहद सावधान नहीं हैं।

+1

मैं कहूंगा कि यह असली दुनिया के उपयोग के मामले में आने वाली समस्या है, कुछ अकादमिक होमवर्क नहीं। मैं व्यक्तिगत रूप से यथासंभव मानक का पालन करता हूं लेकिन वास्तविक जीवन में, कभी-कभी मैं बस नहीं कर सकता। –

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