Почему GCC вставляет mfence там, где Clang его не использует? - PullRequest
6 голосов
/ 19 мая 2019

Почему GCC и Clang генерируют столь разные asm для этого кода (x86_64, -O3 -std = c ++ 17)?

#include <atomic>

int global_var = 0;

int foo_seq_cst(int a)
{
    std::atomic<int> ia;
    ia.store(global_var + a, std::memory_order_seq_cst);
    return ia.load(std::memory_order_seq_cst);
}

int foo_relaxed(int a)
{
    std::atomic<int> ia;
    ia.store(global_var + a, std::memory_order_relaxed);
    return ia.load(std::memory_order_relaxed);
}

GCC 9.1:

foo_seq_cst(int):
        add     edi, DWORD PTR global_var[rip]
        mov     DWORD PTR [rsp-4], edi
        mfence
        mov     eax, DWORD PTR [rsp-4]
        ret
foo_relaxed(int):
        add     edi, DWORD PTR global_var[rip]
        mov     DWORD PTR [rsp-4], edi
        mov     eax, DWORD PTR [rsp-4]
        ret

Clang 8.0:

foo_seq_cst(int):                       # @foo_seq_cst(int)
        mov     eax, edi
        add     eax, dword ptr [rip + global_var]
        ret
foo_relaxed(int):                       # @foo_relaxed(int)
        mov     eax, edi
        add     eax, dword ptr [rip + global_var]
        ret

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

1 Ответ

5 голосов
/ 23 мая 2019

Более реалистичный пример :

#include <atomic>

std::atomic<int> a;

void foo_seq_cst(int b) {
    a = b;
}

void foo_relaxed(int b) {
    a.store(b, std::memory_order_relaxed);
}

gcc-9.1:

foo_seq_cst(int):
        mov     DWORD PTR a[rip], edi
        mfence
        ret
foo_relaxed(int):
        mov     DWORD PTR a[rip], edi
        ret

clang-8.0:

foo_seq_cst(int):                       # @foo_seq_cst(int)
        xchg    dword ptr [rip + a], edi
        ret
foo_relaxed(int):                       # @foo_relaxed(int)
        mov     dword ptr [rip + a], edi
        ret

gccиспользует mfence, тогда как clang использует xchg для std::memory_order_seq_cst.

xchg подразумевает префикс locklock, и mfence удовлетворяют требованиям std::memory_order_seq_cst, что означает отсутствие переупорядочения и общего порядка.

Из Руководства разработчика программного обеспечения для архитектуры Intel 64 и IA-32:

MFENCE - Memory Fence

Выполняет сериализацию всех инструкций загрузки из памяти и сохранения в память, которые были выполнены до инструкции MFENCE.Эта операция сериализации гарантирует, что каждая инструкция загрузки и сохранения, которая предшествует инструкции MFENCE в программном порядке, становится видимой глобально перед любой инструкцией загрузки или сохранения, которая следует за инструкцией MFENCE.Инструкция MFENCE упорядочена в отношении всех инструкций загрузки и хранения, других инструкций MFENCE, любых инструкций LFENCE и SFENCE и любых инструкций сериализации (таких как инструкция CPUID).MFENCE не сериализует поток команд.

8.2.3.8 Заблокированные инструкции имеют общий порядок

Модель упорядочения памяти гарантирует, что все процессоры согласуются на один порядок выполнениявсех заблокированных инструкций, включая те, которые больше 8 байтов или не выровнены естественным образом.

8.2.3.9 Грузы и хранилища не переупорядочиваются с заблокированными инструкциями

Модель упорядочения памяти предотвращает переупорядочение нагрузок и хранилищ с заблокированными инструкциями, которые выполняются раньше или позже.

lock было оценено в 2-3 раза быстрее, чем mfence, и Linux переключился с mfence на lock, где это возможно.

...