C11 Atomic Acquire / Release ve x86_64 yük / mağaza tutarlılığı eksikliği?


10

C11 Standardının 5.1.2.4 Bölümü, özellikle de Serbest Bırakma / Edinme anlambilimi ile mücadele ediyorum. Şunu not ediyorum https://preshing.com/20120913/acquire-and-release-semantics/ (diğerleri arasında) olduğunu belirtmektedir:

... Sürüm semantiği, program sırasından önce gelen herhangi bir okuma veya yazma işlemi ile yazma sürümünün belleğin yeniden sıralanmasını önler.

Yani, aşağıdakiler için:

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 ;
}

bunlar yürütüldüğünde:

>   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) ;

Bu nedenle, "1" iş parçacığının r1 == 1 olmasını ve "2" iş parçacığının r2 = 4 olmasını beklerdim.

Bunu beklerim çünkü (bölüm 5.1.2.4'ün 16. ve 18. paragrafları):

  • (atomik olmayan) tüm okuma ve yazma işlemleri "önce sıralanır" ve bu nedenle "1" iş parçacığında atomik yazma / bırakmadan önce "olur,
  • hangi "thread-inter-thread-before-" "atomik" 2 "dizisinde okuma / alma ('true' okuduğunda),
  • bu da "daha önce dizilenir" ve dolayısıyla "atomik değil" ("2" iş parçacığında) okuma ve yazma işleminden önce gerçekleşir.

Ancak, standardı anlayamıyorum tamamen mümkündür.

X86_64 için oluşturulan kod şunları içerir:

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   

Ve R1 ve X1'in bu sırayla gerçekleşmesi koşuluyla , bu beklediğim sonucu verir.

Ama benim x86_64 anlayışım, okumaların diğer okumalara göre gerçekleşmesi ve diğer yazmalara göre gerçekleşmesi, ancak okumaların ve yazmaların birbiri ile gerçekleşmeyebileceğidir. Bu, X1'in R1'den önce gerçekleşmesi ve hatta X1, X2, W2, R1'in bu sırayla gerçekleşmesi anlamına geliyor - inanıyorum. [Bu umutsuzca görünüyor, ama eğer R1 bazı önbellek sorunları tarafından tutulduysa?]

Lütfen: neyi anlamıyorum?

Ben bir sürü / depolarını değiştirirseniz dikkat ts->readyetmek memory_order_seq_cst, mağazalar için üretilen kod şudur:

  xchg   %cl,(%rdi)

ki bu benim x86_64 anlayışımla tutarlı ve beklediğim sonucu verecek.


5
X86'da, tüm sıradan (geçici olmayan) mağazaların sürüm semantiği vardır. Intel® 64 ve IA-32 Mimarileri Yazılım Geliştirici Kılavuzu Cilt 3 (3A, 3B, 3C ve 3D): Guide Programlama Sistemi , 8.2.3.3 Stores Are Not Reordered With Earlier Loads. Böylece derleyiciniz kodunuzu doğru şekilde çeviriyor (ne kadar şaşırtıcı), böylece kodunuz etkili bir şekilde tamamen sıralı olacak ve aynı anda ilginç bir şey olmayacak.
EOF

Teşekkür ederim ! (Sessizce deliriyordum.) FWIW Bağlantıyı öneririm - özellikle bölüm 3, "Programcı'nın Modeli". Ama içine düştüğüm hatayı önlemek için, "3.1 Soyut Makine" her biri "tek bir sıralı talimat yürütme akışı" (benim vurgu eklendi) olan "donanım iş parçacıkları" olduğunu unutmayın . Şimdi daha az bilişsel uyumsuzluk ile C11 Standardı anlamaya çalışırken dönebilirsiniz :-)
Chris Hall

Yanıtlar:


1

x86'nın bellek modeli temelde sıralı tutarlılık artı bir depolama arabelleğidir (mağaza iletme ile). Yani her mağaza bir yayın mağazasıdır 1 . Bu nedenle sadece seq-cst mağazalarının özel talimatlara ihtiyacı vardır. ( C / C ++ 11 atom eşleşmeleri asm ). Ayrıca, https://stackoverflow.com/tags/x86/info , x86-TSO bellek modelinin resmi bir açıklaması da dahil olmak üzere x86 belgelerine bazı bağlantılara sahiptir (temel olarak çoğu insan için okunamaz; birçok tanımdan geçmeyi gerektirir).

Zaten Jeff Preshing'in mükemmel makale dizisini okuduğunuz için, sizi daha ayrıntılı olarak ele alan başka bir makaleye yönlendireceğim: https://preshing.com/20120930/weak-vs-strong-memory-models/

X86'da izin verilen tek yeniden sıralama, bu terimlerden bahsediyorsak , LoadStore değil StoreLoad'dır . (Bir yük bir mağazanın yalnızca bir kısmıyla örtüşüyorsa mağaza yönlendirme ekstra eğlenceli şeyler yapabilir; Genel olarak Görünmez yükleme talimatları , ancak bunu derleyici tarafından üretilen kodda asla alamazsınız stdatomic.)

@EOF, Intel'in el kitabından doğru alıntı ile yorum yaptı:

Intel® 64 ve IA-32 Mimarileri Yazılım Geliştirici Kılavuzu Cilt 3 (3A, 3B, 3C ve 3D): Sistem Programlama Kılavuzu, 8.2.3.3 Mağazalar Daha Önceki Yüklerle Yeniden Sıralanmaz.


Dipnot 1: zayıf sıralı NT mağazalarını yok saymak; Bu yüzden normalde sfenceNT mağazaları yaptıktan sonra. C11 / C ++ 11 uygulamaları NT depoları kullanmadığınızı varsayar. Eğer öyleyse, _mm_sfenceNT depolarınıza saygı duyduğundan emin olmak için bir serbest bırakma işleminden önce kullanın . (Genel olarak / diğer durumlarda kullanmayın_mm_mfence_mm_sfence ; genellikle derleme zamanı yeniden sıralamasını engellemeniz gerekir. Veya elbette sadece stdatomik kullanın.)


X86-TSO: x86 Çok İşlemcileri için Titiz ve Kullanılabilir Programcı Modeli'ni başvuruda bulunduğunuz (ilgili) Resmi Açıklamadan daha okunabilir buluyorum . Ama asıl amacım C11 / C18 Standardının 5.1.2.4 ve 7.17.3 bölümlerini tam olarak anlamak. Özellikle, Ben Release / Acquire / Acquire + Release olsun düşünüyorum, ancak memory_order_seq_cst ayrı ayrı tanımlanmıştır ve hepsinin birbirine nasıl uygun olduğunu görmek için mücadele ediyorum :-(
Chris Hall

@ChrisHall: Acq / rel'nin tam olarak ne kadar zayıf olabileceğini anlamaya yardımcı olduğunu buldum ve bunun için IRRW yeniden sıralama yapabilen POWER gibi makinelere bakmanız gerekiyor. (seq-cst yasaklar ama acq / rel bunu yapmaz). Farklı iş parçacıklarındaki farklı konumlara iki atom yazımı her zaman diğer iş parçacıkları tarafından aynı sırada mı görünecek? . Ayrıca C ++ 11 StoreLoad bariyerine nasıl ulaşılır? standardın, senkronizasyonlu veya her şeyle-seq-cst vakalarının dışında sipariş vermeyi resmi olarak ne kadar az garanti ettiğini tartışıyor.
Peter Cordes

@ChrisHall: seq-cst'nin yaptığı en önemli şey StoreLoad yeniden siparişini engellemek. (X86'da acq / rel'nin ötesinde yaptığı tek şey budur). preshing.com/20120515/memory-reordering-caught-in-the-act asm kullanır, ancak bu seq-cst ile acq / rel arasındaki karşılığıdır
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.