C11 परमाणु अधिग्रहण / रिलीज और x86_64 लोड / स्टोर जुटना की कमी?


10

मैं सी 11 मानक की धारा 5.1.2.4 के साथ संघर्ष कर रहा हूं, विशेष रूप से रिलीज़ / एक्वायर का शब्दार्थ। मैं ध्यान देता हूं कि https://preshing.com/20120913/acquire-and-release-semantics/ (अन्य लोगों के अनुसार):

... रिलीज शब्दार्थ किसी भी पढ़ने या लिखने के संचालन के साथ लिखने-जारी करने की स्मृति को पुन: व्यवस्थित करने से रोकते हैं जो इसे कार्यक्रम क्रम में पूर्ववर्ती करते हैं।

तो, निम्नलिखित के लिए:

typedef struct test_struct
{
  _Atomic(bool) ready ;
  int  v1 ;
  int  v2 ;
} test_struct_t ;

extern void
test_init(test_struct_t* ts, int v1, int v2)
{
  ts->v1 = v1 ;
  ts->v2 = v2 ;
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}

extern int
test_thread_1(test_struct_t* ts, int v2)
{
  int v1 ;
  while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v2 = v2 ;       // expect read to happen before store/release 
  v1     = ts->v1 ;   // expect write to happen before store/release 
  atomic_store_explicit(&ts->ready, true, memory_order_release) ;
  return v1 ;
}

extern int
test_thread_2(test_struct_t* ts, int v1)
{
  int v2 ;
  while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v1 = v1 ;
  v2     = ts->v2 ;   // expect write to happen after store/release in thread "1"
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
  return v2 ;
}

जहां उन पर अमल किया जाता है:

>   in the "main" thread:  test_struct_t ts ;
>                          test_init(&ts, 1, 2) ;
>                          start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
>                          start thread "1" which does: r1 = test_thread_1(&ts, 4) ;

इसलिए, मैं r1 == 1 के लिए थ्रेड "1" और r2 = 4 के लिए थ्रेड "2" की अपेक्षा करूंगा।

मैं उम्मीद करूंगा कि क्योंकि (संप्रदाय 16 और 18 संप्रदाय 5.1.2.4 का अनुसरण करता है):

  • सभी (परमाणु नहीं) पढ़ता है और लिखता है "पहले से अनुक्रमित है" और इसलिए "परमाणु लेखन / रिलीज से पहले" धागे में "1" होता है,
  • जो "अंतर-धागा-होता है-उससे पहले" परमाणु "/ 2 धागे में प्राप्त" (जब यह 'सत्य' पढ़ता है),
  • जो बदले में "पहले से अनुक्रमित" है और इसलिए "परमाणु से पहले" होता है (ना कि परमाणु) पढ़ता है और लिखता है (थ्रेड "2" में)।

हालांकि, यह पूरी तरह से संभव है कि मैं मानक को समझने में विफल रहा हूं।

मैं देखता हूं कि x86_64 के लिए उत्पन्न कोड में शामिल हैं:

test_thread_1:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  jne    <test_thread_1>  -- while is true
  mov    %esi,0x8(%rdi)   -- (W1) ts->v2 = v2
  mov    0x4(%rdi),%eax   -- (R1) v1     = ts->v1
  movb   $0x1,(%rdi)      -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
  retq   

test_thread_2:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  je     <test_thread_2>  -- while is false
  mov    %esi,0x4(%rdi)   -- (W2) ts->v1 = v1
  mov    0x8(%rdi),%eax   -- (R2) v2     = ts->v2   
  movb   $0x0,(%rdi)      -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
  retq   

और बशर्ते कि आर 1 और एक्स 1 उस क्रम में हो, इससे मुझे अपेक्षित परिणाम मिलता है।

लेकिन x86_64 के बारे में मेरी समझ यह है कि रीड्स अन्य रीड्स के साथ होता है और राइट्स अन्य राइट्स के साथ होता है, लेकिन रीड एंड राइट एक दूसरे के साथ नहीं होता है। इसका मतलब यह है कि यह X1 के लिए R1 से पहले संभव है, और यहां तक ​​कि X1, X2, W2, R1 के लिए भी उसी क्रम में होना संभव है - मेरा मानना ​​है। [यह काफी संभावना नहीं है, लेकिन अगर R1 कुछ कैश मुद्दों द्वारा आयोजित किया गया था?]

कृपया: मैं क्या समझ नहीं रहा हूँ?

मैं नोट करता हूं कि अगर मैं लोड / स्टोर को बदल देता हूं, तो स्टोर के ts->readyलिए memory_order_seq_cstउत्पन्न कोड है:

  xchg   %cl,(%rdi)

जो कि x86_64 की मेरी समझ के अनुरूप है और मुझे अपेक्षित परिणाम देगा।


5
X86 पर, सभी साधारण (गैर-अस्थायी नहीं) स्टोर में रिलीज़ शब्दार्थ हैं। इंटेल 64 और IA-32 आर्किटेक्चर सॉफ्टवेयर डेवलपर की मैनुअल माप 3 (3 ए, 3 बी, 3 सी और 3 डी): सिस्टम गाइड प्रोग्रामिंग , 8.2.3.3 Stores Are Not Reordered With Earlier Loads। तो आपका कंपाइलर आपके कोड (कितनी आश्चर्यजनक) का सही अनुवाद कर रहा है, जैसे कि आपका कोड प्रभावी रूप से पूरी तरह से अनुक्रमिक है और कुछ भी दिलचस्प नहीं है।
EOF

धन्यवाद ! (मैं चुपचाप बोनट पर जा रहा था।) FWIW मैं लिंक की सिफारिश करता हूं - विशेष रूप से खंड 3, "प्रोग्रामर का मॉडल"। लेकिन गलती से बचने के लिए, मैं ध्यान देता हूं कि "3.1 द एब्सट्रैक्ट मशीन" में "हार्डवेयर थ्रेड्स" हैं, जिनमें से प्रत्येक " इंस्ट्रक्शन एक्जीक्यूशन का एक सिंगल -ऑर्डर स्ट्रीम" है (मेरा जोर जोड़ा)। मैं अब C11 मानक को समझने की कोशिश कर रहा हूँ ... कम संज्ञानात्मक असंगति के साथ :-)
क्रिस हॉल

जवाबों:


1

x86 का मेमोरी मॉडल मूल रूप से अनुक्रमिक-संगति और स्टोर बफर (स्टोर अग्रेषण के साथ) है। इसलिए हर दुकान एक रिलीज़-स्टोर 1 है । यही कारण है कि केवल seq-cst स्टोर्स को किसी विशेष निर्देश की आवश्यकता होती है। ( सी / सी ++ 11 एटमिक्स मैपिंग टू एसम )। इसके अलावा, https://stackoverflow.com/tags/x86/info में x86 डॉक्स से कुछ लिंक हैं, जिसमें x86-TSO मेमोरी मॉडल का औपचारिक विवरण (मूल रूप से अधिकांश मनुष्यों के लिए अपठनीय है; बहुत अधिक परिभाषाओं के माध्यम से वैडिंग की आवश्यकता होती है)।

चूँकि आप पहले से ही जेफ प्रेशिंग के लेखों की उत्कृष्ट श्रृंखला पढ़ रहे हैं, इसलिए मैं आपको एक और बिंदु पर बताऊंगा जो अधिक विवरण में है: https://preshing.com/20120930/weak-vs-strong-memory-models/

यदि केवल x86 पर अनुमति दी गई है , तो फिर से लोड किया जा रहा है, केवल लोडलेयर नहीं है , यदि हम उन शब्दों में बात कर रहे हैं। (स्टोर फ़ॉरवर्डिंग अतिरिक्त मज़ेदार सामग्री कर सकता है यदि लोड केवल आंशिक रूप से एक स्टोर को ओवरलैप करता है; विश्व स्तर पर अदृश्य लोड निर्देश , हालांकि आपको इसके लिए कंपाइलर-जनरेट किए गए कोड में कभी नहीं मिलेगा stdatomic।)

@ ईओएफ ने इंटेल के मैनुअल से सही उद्धरण के साथ टिप्पणी की:

Intel® 64 और IA-32 आर्किटेक्चर सॉफ्टवेयर डेवलपर का मैनुअल वॉल्यूम 3 (3A, 3B, 3C और 3D): सिस्टम प्रोग्रामिंग गाइड, 8.2.3.3 स्टोर्स पहले लोर्ड्स के साथ रीऑर्डर नहीं किए गए हैं।


फुटनोट 1: कमजोर-ऑर्डर किए गए NT स्टोरों की अनदेखी; यही कारण है कि आप आम तौर sfenceपर NT स्टोर करने के बाद। C11 / C ++ 11 कार्यान्वयन मान लेते हैं कि आप NT स्टोर का उपयोग नहीं कर रहे हैं। यदि आप हैं, _mm_sfenceतो यह सुनिश्चित करने के लिए रिलीज़ ऑपरेशन से पहले उपयोग करें कि यह आपके NT स्टोर का सम्मान करता है। (सामान्य रूप से अन्य मामलों में / का उपयोग नहीं करते हैं_mm_mfence_mm_sfence ; आमतौर पर आपको केवल संकलन-समय पुन: व्यवस्थित करने की आवश्यकता होती है। या निश्चित रूप से केवल स्टैडाटोमिक का उपयोग करें।)


मुझे लगता है कि x86-TSO: एक कठोर और प्रयोग करने योग्य प्रोग्रामर का मॉडल x86 मल्टीप्रोसेसरों के लिए (संबंधित) औपचारिक विवरण की तुलना में अधिक पठनीय है । लेकिन मेरी वास्तविक महत्वाकांक्षा C11 / C18 स्टैंडर्ड के खंड 5.1.2.4 और 7.17.3 को पूरी तरह समझने की है। विशेष रूप से, मुझे लगता है कि मुझे रिलीज़ / एक्वायर / एक्वायर / रिलीज़ मिल रही है, लेकिन memory_order_seq_cst को अलग से परिभाषित किया गया है और मैं यह देखने के लिए संघर्ष कर रहा हूँ कि वे सभी एक साथ कैसे फिट होते हैं :-(
क्रिस हॉल

@ क्रिसल: मैंने पाया कि यह वास्तव में यह समझने में मदद करता है कि एसक्यू / रिले कितना कमजोर हो सकता है, और इसके लिए आपको पॉवर जैसी मशीनों को देखने की जरूरत है जो आईआरआईडब्ल्यू पुन: व्यवस्थित कर सकती हैं। (जो seq-cst मना करता है लेकिन acq / rel नहीं करता है)। क्या दो परमाणु अलग-अलग थ्रेड में अलग-अलग स्थानों पर लिखते हैं, हमेशा एक ही क्रम में अन्य थ्रेड्स द्वारा देखे जाते हैं? । इसके अलावा C ++ 11 में StoreLoad बाधा कैसे प्राप्त करें? के बारे में कुछ चर्चा है कि कैसे मानक मानक sychronizes के साथ बाहर के बारे में गारंटी देता है या सब कुछ seq-cst मामलों के साथ।
पीटर कॉर्ड्स

@ क्रिसहॉल: मुख्य बात seq-cst करता है, ब्लॉक स्टोरड रीऑर्डरिंग है। (X86 पर यह एकमात्र ऐसी चीज़ है जो इसे acq / rel से परे करती है)। Preshing.com/20120515/memory-reordering-caught-in-the- asm का उपयोग करता है, लेकिन यह seq-cst बनाम acq / rel के बराबर है
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.