Как работает std :: memory_order_XXX - PullRequest
0 голосов
/ 14 июня 2019

Я не понимаю, как работает std :: memory_order_XXX (например, memory_order_release / memory_order_acquire ...).

Из некоторых документов видно, что эти режимы памяти имеют различную функцию , но я действительно запутался, что у них одинаковый код сборки, , что определило различия ?

Этот код:

    static std::atomic<long> gt;
     void test1() {
          gt.store(1, std::memory_order_release);
          gt.store(2, std::memory_order_relaxed);
          gt.load(std::memory_order_acquire);
          gt.load(std::memory_order_relaxed);
     }

Соответствует:

        00000000000007a0 <_Z5test1v>:
         7a0:   55                      push   %rbp
         7a1:   48 89 e5                mov    %rsp,%rbp
         7a4:   48 83 ec 30             sub    $0x30,%rsp

**memory_order_release:
         7a8:   48 c7 45 f8 01 00 00    movq   $0x1,-0x8(%rbp)
         7af:   00 
         7b0:   c7 45 e8 03 00 00 00    movl   $0x3,-0x18(%rbp)
         7b7:   8b 45 e8                mov    -0x18(%rbp),%eax
         7ba:   be ff ff 00 00          mov    $0xffff,%esi
         7bf:   89 c7                   mov    %eax,%edi
         7c1:   e8 b1 00 00 00          callq  877 <_ZStanSt12memory_orderSt23__memory_order_modifier>
         7c6:   89 45 ec                mov    %eax,-0x14(%rbp)
         7c9:   48 8b 55 f8             mov    -0x8(%rbp),%rdx
         7cd:   48 8d 05 44 08 20 00    lea    0x200844(%rip),%rax        # 201018 <_ZL2gt>
         7d4:   48 89 10                mov    %rdx,(%rax)
         7d7:   0f ae f0                mfence** 

**memory_order_relaxed:
         7da:   48 c7 45 f0 02 00 00    movq   $0x2,-0x10(%rbp)
         7e1:   00 
         7e2:   c7 45 e0 00 00 00 00    movl   $0x0,-0x20(%rbp)
         7e9:   8b 45 e0                mov    -0x20(%rbp),%eax
         7ec:   be ff ff 00 00          mov    $0xffff,%esi
         7f1:   89 c7                   mov    %eax,%edi
         7f3:   e8 7f 00 00 00          callq  877 <_ZStanSt12memory_orderSt23__memory_order_modifier>
         7f8:   89 45 e4                mov    %eax,-0x1c(%rbp)
         7fb:   48 8b 55 f0             mov    -0x10(%rbp),%rdx
         7ff:   48 8d 05 12 08 20 00    lea    0x200812(%rip),%rax        # 201018 <_ZL2gt>
         806:   48 89 10                mov    %rdx,(%rax)
         809:   0f ae f0                mfence** 

**memory_order_acquire:
         80c:   c7 45 d8 02 00 00 00    movl   $0x2,-0x28(%rbp)
         813:   8b 45 d8                mov    -0x28(%rbp),%eax
         816:   be ff ff 00 00          mov    $0xffff,%esi
         81b:   89 c7                   mov    %eax,%edi
         81d:   e8 55 00 00 00          callq  877 <_ZStanSt12memory_orderSt23__memory_order_modifier>
         822:   89 45 dc                mov    %eax,-0x24(%rbp)
         825:   48 8d 05 ec 07 20 00    lea    0x2007ec(%rip),%rax        # 201018 <_ZL2gt>
         82c:   48 8b 00                mov    (%rax),%rax**

**memory_order_relaxed:
         82f:   c7 45 d0 00 00 00 00    movl   $0x0,-0x30(%rbp)
         836:   8b 45 d0                mov    -0x30(%rbp),%eax
         839:   be ff ff 00 00          mov    $0xffff,%esi
         83e:   89 c7                   mov    %eax,%edi
         840:   e8 32 00 00 00          callq  877 <_ZStanSt12memory_orderSt23__memory_order_modifier>
         845:   89 45 d4                mov    %eax,-0x2c(%rbp)
         848:   48 8d 05 c9 07 20 00    lea    0x2007c9(%rip),%rax        # 201018 <_ZL2gt>
         84f:   48 8b 00                mov    (%rax),%rax**

         852:   90                      nop
         853:   c9                      leaveq 
         854:   c3                      retq   

        00000000000008cc <_ZStanSt12memory_orderSt23__memory_order_modifier>:
         8cc:   55                      push   %rbp
         8cd:   48 89 e5                mov    %rsp,%rbp
         8d0:   89 7d fc                mov    %edi,-0x4(%rbp)
         8d3:   89 75 f8                mov    %esi,-0x8(%rbp)
         8d6:   8b 55 fc                mov    -0x4(%rbp),%edx
         8d9:   8b 45 f8                mov    -0x8(%rbp),%eax
         8dc:   21 d0                   and    %edx,%eax
         8de:   5d                      pop    %rbp
         8df:   c3                      retq   

I Ожидается, что разные режимы памяти имеют разные инструменты при сборке кода , ноустановка другого значения режима не влияет на сборку , кто это может объяснить?

Ответы [ 2 ]

3 голосов
/ 15 июня 2019

Учитывая код:

#include <atomic>

static std::atomic<long> gt;

void test1() {
    gt.store(41, std::memory_order_release);
    gt.store(42, std::memory_order_relaxed);
    gt.load(std::memory_order_acquire);
    gt.load(std::memory_order_relaxed);
}

На приличном уровне оптимизации нет сборки мусора, перемещающей значения в регистрах, кроме стека:

test1():
        movq    $41, gt(%rip)
        movq    $42, gt(%rip)
        movq    gt(%rip), %rax
        movq    gt(%rip), %rax
        ret

Мы видим, что тот же самый кодгенерируется для разных порядков памяти;хотя тестирование различных инструкций в одной и той же функции по порядку является очень плохой практикой, поскольку инструкции C ++ не должны компилироваться независимо, и контекст может влиять на генерацию кода.Но с текущей генерацией кода в GCC он компилирует каждый оператор, включающий атомарный, как свой собственный.Хорошая практика - иметь разные функции для каждого оператора.

Здесь генерируется тот же код, потому что для этих порядков памяти не требуется никаких специальных инструкций .

3 голосов
/ 14 июня 2019

Каждая настройка модели памяти имеет свою семантику. Компилятор обязан удовлетворить эту семантику, что означает:

  1. Запрещает компилятору выполнять определенные оптимизации, такие как изменение порядка чтения и записи.

  2. Он дает указание компилятору распространить то же сообщение на оборудование. Как это сделать, зависит от платформы. Сам x86_64 обеспечивает очень сильную модель памяти. Следовательно, почти во всех случаях вы не увидите никакой разницы в сгенерированном ассемблерном коде для x86_64 независимо от того, какую модель памяти вы выберете. Однако в архитектурах RISC (например, ARM) вы увидите разницу, потому что компилятору придется вставлять барьеры памяти. Тип барьера памяти зависит от выбранной модели памяти.

РЕДАКТИРОВАТЬ: Посмотрите на JSR-133 . Он очень старый и относится к Java, но он дает самое хорошее объяснение модели памяти с точки зрения компилятора, которую я знаю. В частности, посмотрите на таблицу инструкций по барьеру памяти для разных архитектур.

...