C11 Atomic Acquire / Release 및 x86_64로드 / 스토어 일관성 부족?


10

C11 표준의 5.1.2.4 섹션, 특히 Release / Acquire의 의미론으로 어려움을 겪고 있습니다. 나는주의 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가있을 것으로 예상합니다.

나는 (5.1.2.4의 16 항과 18 항에 따름)

  • 모든 (원자 아님) 읽기 및 쓰기는 "이전 순서"로되어 있으므로 "1"스레드의 원자 쓰기 / 해제 "전에"발생합니다.
  • 스레드 "2"의 원자 읽기 / 획득 ( 'true'를 읽을 때)
  • 이것은 "이전에 순서화"되고 따라서 "원자가 아닌" "스레드 전에"읽고 (쓰레드 "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에서 모든 임시 저장소가 아닌 일반 저장소에는 릴리스 의미가 있습니다. 인텔 ® 64 및 IA-32 아키텍처 소프트웨어 개발자 설명서 제 3 권 (3A, 3B, 3C 및 3D) : 가이드 프로그래밍 시스템 , 8.2.3.3 Stores Are Not Reordered With Earlier Loads. 따라서 컴파일러는 코드가 올바르게 완전히 번역되어 (놀랍습니다) 코드가 효과적으로 순차적이며 흥미로운 것은 동시에 발생하지 않습니다.
EOF

감사합니다 ! (나는 조용히 갔다.) FWIW 나는 링크 , 특히 섹션 3, "프로그래머의 모델"을 추천한다. 그러나 "3.1 추상 기계"의 "하나입니다 각각의"하드웨어 스레드 "이 있다는 것을 나는 참고로 떨어졌다 실수를 방지하기 에 주문 명령 실행의 흐름"(내 강조 추가). 나는 이제 C11 표준을 이해하려고 노력할 수 있습니다 ...인지 불협화음이 적습니다 :-)
Chris Hall

답변:


1

x86의 메모리 모델은 기본적으로 순차 일관성과 저장 버퍼 (저장 포워딩 포함)입니다. 따라서 모든 상점은 릴리스 저장소 1 입니다. 따라서 seq-cst 상점에만 특별한 지침이 필요합니다. ( asm에 대한 C / C ++ 11 원자 매핑 ). 또한 https://stackoverflow.com/tags/x86/info 에는 x86-TSO 메모리 모델에 대한 공식적인 설명을 포함하여 x86 문서에 대한 링크가 있습니다 (기본적으로 대부분의 사람이 읽을 수 없으며 많은 정의를 넘어 섭니다).

Jeff Preshing의 훌륭한 기사 시리즈를 이미 읽고 있으므로 더 자세히 설명 할 다른 기사를 알려 드리겠습니다. https://preshing.com/20120930/weak-vs-strong-memory-models/

x86에서 허용되는 유일한 재정렬은 LoadStore가 아닌 StoreLoad 입니다. (부하 전달은로드가 부분적으로 만 겹치는 경우 추가로 재미있는 일을 할 수 있습니다. 전 세계적으로 보이지 않는로드 명령어 는 컴파일러 생성 코드에서 결코 얻을 수는 없지만 stdatomic)

@EOF는 인텔 설명서의 올바른 인용문으로 주석을 달았습니다.

인텔 ® 64 및 IA-32 아키텍처 소프트웨어 개발자 설명서 3 권 (3A, 3B, 3C 및 3D) : 시스템 프로그래밍 안내서, 8.2.3.3 이전로드로 상점이 재주문되지 않습니다.


각주 1 : 약한 순서의 NT 상점 무시 이것이 sfenceNT 저장소 를 정상적으로 수행 한 후의 이유 입니다. C11 / C ++ 11 구현에서는 NT 저장소를 사용하지 않는다고 가정합니다. 있는 경우 _mm_sfence릴리스 조작 전에 사용 하여 NT 저장소를 존중하는지 확인하십시오. (일반적으로 / 다른 경우를 사용하지 마십시오_mm_mfence_mm_sfence . 일반적으로 컴파일 타임 재정렬을 차단하기 만하면됩니다. 또는 물론 표준을 사용하십시오.)


나는 찾을 엄격한 및 x86 멀티 프로세서에 사용 가능한 프로그래머 모델 : 86 - TSO를 (관련)보다 더 많은 읽을 공식 설명 은 참조. 그러나 나의 진정한 야심은 C11 / C18 표준의 5.1.2.4 및 7.17.3 섹션을 완전히 이해하는 것입니다. 특히, 나는 Release / Acquire / Acquire + Release를 얻는다고 생각하지만 memory_order_seq_cst는 별도로 정의되어 있으며 그것들이 모두 어떻게 조화를 이루고 있는지 고심하고 있습니다 :-(
Chris Hall

@ChrisHall : acq / rel의 약한 정도를 정확히 이해하는 데 도움이되었으며, IRIW 재정렬을 수행 할 수있는 POWER와 같은 기계를 살펴 봐야한다는 것을 알았습니다. (seq-cst는 금지하지만 acq / rel은 금지합니다). 다른 스레드에서 다른 위치에 대한 두 개의 원자 쓰기가 항상 다른 스레드에서 동일한 순서로 표시됩니까? . 또한 C ++ 11에서 StoreLoad 장벽을 달성하는 방법은 무엇입니까? 은 표준 또는 모든 경우-cst 케이스 외부에서 주문하는 것에 대해 표준이 공식적으로 보장하는 정도에 대한 토론을했습니다.
Peter Cordes

@ChrisHall : seq-cst가 수행하는 주요 작업은 StoreLoad 재정렬 차단입니다. (x86에서는 이것이 acq / rel을 넘어서는 유일한 것입니다). preshing.com/20120515/memory-reordering-cact-in-the-act 는 asm을 사용하지만 seq-cst vs. acq / rel과 동일
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.