C11 Atomic Acquire / Release และ x86_64 ขาดการเชื่อมโยงโหลด / จัดเก็บ?


10

ฉันกำลังดิ้นรนกับมาตรา 5.1.2.4 ของมาตรฐาน C11 โดยเฉพาะความหมายของการเปิดตัว / การได้มา ฉันทราบว่า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) ;

ดังนั้นฉันคาดว่าเธรด "1" จะมี r1 == 1 และเธรด "2" เพื่อให้มี r2 = 4

ฉันคาดหวังว่าเพราะ (ดังต่อไปนี้กาฝาก 16 และ 18 ของภาค 5.1.2.4):

  • การอ่านและการเขียน (ไม่ใช่อะตอมมิก) ทั้งหมดนั้นเป็น "ลำดับก่อนหน้า" และด้วยเหตุนี้ "เกิดขึ้นก่อน" อะตอมเขียน / รีลีสในเธรด "1"
  • สิ่งที่ "อินเตอร์เธรดเกิดขึ้นก่อน" อะตอมอ่าน / รับในเธรด "2" (เมื่ออ่าน 'จริง')
  • ซึ่งในทางกลับกันคือ "sequenced before" และด้วยเหตุนี้ "เกิดขึ้นก่อน" (ไม่ใช่ atomic) อ่านและเขียน (ใน thread "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   

และหากว่า R1 และ X1 เกิดขึ้นตามลำดับสิ่งนี้จะให้ผลลัพธ์ที่ฉันคาดหวัง

แต่ความเข้าใจของฉันเกี่ยวกับ x86_64 คือการอ่านเกิดขึ้นตามลำดับกับการอ่านและการเขียนอื่น ๆ เกิดขึ้นตามลำดับกับการเขียนอื่น ๆ แต่การอ่านและการเขียนอาจไม่เกิดขึ้นตามลำดับ ซึ่งหมายความว่ามันเป็นไปได้สำหรับ X1 ที่จะเกิดขึ้นก่อน R1 และแม้กระทั่งสำหรับ X1, X2, W2, R1 ที่จะเกิดขึ้นตามลำดับ - ฉันเชื่อว่า [ดูเหมือนว่าไม่น่าจะหมดหวัง แต่หาก R1 มีปัญหาเกี่ยวกับแคชบ้าง]

ได้โปรด: ฉันไม่เข้าใจอะไร

ผมทราบว่าถ้าผมเปลี่ยนโหลด / ร้านค้าของts->readyที่จะmemory_order_seq_cstรหัสที่สร้างขึ้นสำหรับร้านค้าคือ

  xchg   %cl,(%rdi)

ซึ่งสอดคล้องกับความเข้าใจของฉันใน x86_64 และจะให้ผลลัพธ์ที่ฉันคาดหวัง


5
ใน x86 ร้านค้าทั่วไป (ไม่ใช่ที่ไม่ใช่ชั่วคราว) ทั้งหมดได้ปล่อยซีแมนทิกส์ Intel® 64 และ IA-32 สถาปัตยกรรมซอฟต์แวร์ของนักพัฒนาคู่มือเล่ม 3 (3A, 3B, 3C & 3D): ระบบการเขียนโปรแกรมคู่มือ8.2.3.3 Stores Are Not Reordered With Earlier Loads , ดังนั้นคอมไพเลอร์ของคุณแปลโค้ดของคุณอย่างถูกต้อง (น่าแปลกใจมาก) เช่นนั้นโค้ดของคุณนั้นเรียงตามลำดับอย่างสมบูรณ์และไม่มีอะไรน่าสนใจเกิดขึ้นพร้อมกัน
EOF

ขอบคุณ ! (ฉันเป็นคนทำเงินอย่างเงียบ ๆ ) FWIW ฉันแนะนำลิงค์ - โดยเฉพาะอย่างยิ่งในส่วนที่ 3 "Programmer's Model" แต่การที่จะหลีกเลี่ยงความผิดพลาดที่ผมก้มลงบันทึกว่าใน "3.1 บทคัดย่อเครื่อง" มี "หัวข้อฮาร์ดแวร์" แต่ละแห่งซึ่งเป็น "หนึ่งเดียวในการสั่งซื้อกระแสของการเรียนการสอนการดำเนินการ" (เน้นของฉันเพิ่ม) ตอนนี้ฉันสามารถกลับไปพยายามทำความเข้าใจกับมาตรฐาน C11 ... ด้วยความไม่ลงรอยกันของความรู้ความเข้าใจที่น้อยลง :-)
Chris Hall

คำตอบ:


1

โมเดลหน่วยความจำของ x86 นั้นมีความสอดคล้องตามลำดับพร้อมกับบัฟเฟอร์ของร้านค้า ดังนั้นทุกสาขาเป็นรุ่นร้าน1 นี่คือเหตุผลที่เฉพาะร้านค้า seq-cst ต้องการคำแนะนำพิเศษใด ๆ ( การแม็ป atomics C / C ++ 11 ไปยัง asm ) นอกจากนี้https://stackoverflow.com/tags/x86/infoมีลิงก์ไปยัง x86 docs รวมถึงคำอธิบายอย่างเป็นทางการของรุ่นหน่วยความจำ x86-TSO (โดยทั่วไปไม่สามารถอ่านได้สำหรับมนุษย์ส่วนใหญ่ต้องลุยผ่านข้อกำหนดจำนวนมาก)

เนื่องจากคุณได้อ่านบทความที่ยอดเยี่ยมของ Jeff Preshing ฉันจะนำคุณไปสู่บทความที่มีรายละเอียดเพิ่มเติม: https://preshing.com/20120930/weak-vs-strong-memory-models/

การเรียงลำดับใหม่ที่อนุญาตบน x86 คือ StoreLoad ไม่ใช่ LoadStoreหากเรากำลังพูดถึงเงื่อนไขเหล่านั้น (การส่งต่อร้านค้าสามารถทำสิ่งที่สนุกเป็นพิเศษได้หากการโหลดทับบางส่วนของร้านค้าเท่านั้นคำแนะนำการโหลดที่มองไม่เห็นทั่วโลกแม้ว่าคุณจะไม่ได้รับสิ่งนั้นในโค้ดที่สร้างโดยคอมไพเลอร์stdatomic)

@EOF แสดงความคิดเห็นพร้อมใบเสนอราคาที่ถูกต้องจากคู่มือของ Intel:

คู่มือสำหรับนักพัฒนาซอฟท์แวร์สถาปัตยกรรม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ในกรณีอื่น ๆโดยปกติคุณจะต้องบล็อกการเรียงลำดับเวลาคอมไพล์ใหม่เท่านั้นหรือใช้ stdatomic แน่นอน)


ฉันค้นหาx86-TSO: โมเดลของโปรแกรมเมอร์ที่จริงจังและใช้งานได้สำหรับ x86 มัลติโพรเซสเซอร์ที่สามารถอ่านได้มากกว่าคำอธิบายอย่างเป็นทางการ (ที่เกี่ยวข้อง) ที่คุณอ้างถึง แต่ความใฝ่ฝันที่แท้จริงของฉันคือการเข้าใจส่วน 5.1.2.4 และ 7.17.3 ของมาตรฐาน C11 / C18 อย่างสมบูรณ์ โดยเฉพาะอย่างยิ่งฉันคิดว่าฉันได้รับ Release / Acquire / Acquire + Release แต่ memory_order_seq_cst มีการกำหนดแยกต่างหากและฉันพยายามที่จะดูว่าพวกเขาเข้ากันได้อย่างไร :-(
Chris Hall

@ChrisHall: ฉันพบว่ามันช่วยให้รู้ว่า acq / rel อ่อนแอแค่ไหนและสำหรับสิ่งที่คุณต้องดูที่เครื่องอย่าง POWER ที่สามารถสั่ง IRIW ใหม่ได้ (ซึ่ง seq-cst ห้าม แต่ acq / rel ไม่ได้) อะตอมสองตัวจะถูกเขียนไปยังตำแหน่งต่าง ๆ ในเธรดที่แตกต่างกันจะสามารถเห็นได้ตามลำดับเดียวกันโดยเธรดอื่นหรือไม่ . นอกจากนี้วิธีการบรรลุอุปสรรค StoreLoad ใน C ++ 11? มีการพูดคุยกันว่ามาตรฐานรับรองอย่างเป็นทางการเพียงเล็กน้อยเกี่ยวกับการสั่งซื้อนอก sychronizes - with หรือ case-seq-cst case
Peter Cordes

@ChrisHall: สิ่งสำคัญที่ seq-cst ทำคือบล็อกการจัดลำดับใหม่ของ StoreLoad (บน x86 นั่นเป็นสิ่งเดียวที่มันทำได้มากกว่า acq / rel) preshing.com/20120515/memory-reordering-caught-in-the-actใช้ asm แต่มันเทียบเท่ากับ seq-cst vs. acq / rel
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.