Рассмотрим следующий код, который использует std::atomic
для атомарной загрузки 64-битного объекта.
#include <atomic>
struct A {
int32_t x, y;
};
A f(std::atomic<A>& a) {
return a.load(std::memory_order_relaxed);
}
С G CC происходят хорошие вещи, и генерируется следующий код. (https://godbolt.org/z/zS53ZF)
f(std::atomic<A>&):
mov rax, QWORD PTR [rdi]
ret
Это именно то, что я ожидал, так как не вижу причин, почему 64-битная структура не может рассматриваться как любая другое 64-битное слово в этой ситуации.
С Clang, однако, история другая. Clang генерирует следующее. (https://godbolt.org/z/d6uqrP)
f(std::atomic<A>&): # @f(std::atomic<A>&)
push rax
mov rsi, rdi
mov rdx, rsp
mov edi, 8
xor ecx, ecx
call __atomic_load
mov rax, qword ptr [rsp]
pop rcx
ret
mov rdi, rax
call __clang_call_terminate
__clang_call_terminate: # @__clang_call_terminate
push rax
call __cxa_begin_catch
call std::terminate()
Это проблематично c для меня по нескольким причинам:
- Более очевидно, есть гораздо больше инструкций, поэтому я ожидаю, что код будет менее эффективным
- Менее очевидно, обратите внимание, что сгенерированный код также включает в себя вызов библиотечной функции
__atomic_load
, что означает, что мой двоичный файл должен быть связан с libatomi c. Это означает, что мне нужны разные списки библиотек для связи в зависимости от того, использует ли пользователь моего кода G CC или Clang. - Функция библиотеки может использовать блокировку, что приведет к снижению производительности
Важный вопрос, который у меня сейчас возникает, заключается в том, есть ли способ заставить Clang также преобразовать нагрузку в одну инструкцию. Мы используем это как часть библиотеки, которую планируем распространять среди других, поэтому мы не можем полагаться на конкретный используемый компилятор. Решение, предложенное мне до сих пор, состоит в том, чтобы использовать тип punning и хранить структуру внутри объединения вместе с 64-битным int, поскольку Clang правильно загружает 64-битные целые числа атомарно в одной инструкции. Однако я скептически отношусь к этому решению, поскольку, хотя оно, похоже, работает на всех основных компиляторах, я прочитал, что на самом деле это неопределенное поведение. Такой код также не особенно удобен для чтения и понимания другими, если они не знакомы с уловкой.
Подводя итог, можно ли атомарно загрузить 64-битную структуру, которая:
- Работает как в Clang, так и в G CC, и, предпочтительно, в большинстве других популярных компиляторов,
- Генерирует одну команду при компиляции,
- Не неопределенное поведение,
- Читатель дружелюбен?