Да, на обычных процессорах, где atomic<bool>
и atomic<int>
также не блокируются, это похоже на atomic<bool>
, используя те же инструкции. (x86 и x86-64 имеют одинаковый набор доступных операций atomi c.)
Можно подумать, что он всегда будет использовать x86 lock bts
или lock btr
для установки / сброса (очистки) одного немного, но может быть более эффективно делать другие вещи (особенно для функции, которая возвращает bool вместо ветвления на нем). Объект представляет собой целый байт, поэтому вы можете просто сохранить или обменять весь байт. (И если ABI гарантирует, что значение всегда 0
или 1
, вам не нужно логизировать его, прежде чем возвращать результат как bool
)
G CC и clang скомпилируйте test_and_set
для обмена байтами и очистите для хранилища байтов 0
. Мы получаем (почти) идентичный asm для atomic_flag
test_and_set
как f.exchange(true);
#include <atomic>
bool TAS(std::atomic_flag &f) {
return f.test_and_set();
}
bool TAS_bool(std::atomic<bool> &f) {
return f.exchange(true);
}
void clear(std::atomic_flag &f) {
//f = 0; // deleted
f.clear();
}
void clear_relaxed(std::atomic_flag &f) {
f.clear(std::memory_order_relaxed);
}
void bool_clear(std::atomic<bool> &f) {
f = false; // deleted
}
На Godbolt для x86-64 с g cc и clang, а также для ARMv7 и AArch64.
## GCC9.2 -O3 for x86-64
TAS(std::atomic_flag&):
mov eax, 1
xchg al, BYTE PTR [rdi]
ret
TAS_bool(std::atomic<bool>&):
mov eax, 1
xchg al, BYTE PTR [rdi]
test al, al
setne al # missed optimization, doesn't need to booleanize to 0/1
ret
clear(std::atomic_flag&):
mov BYTE PTR [rdi], 0
mfence # memory fence to drain store buffer before future loads
ret
clear_relaxed(std::atomic_flag&):
mov BYTE PTR [rdi], 0 # x86 stores are already mo_release, no barrier
ret
bool_clear(std::atomic<bool>&):
mov BYTE PTR [rdi], 0
mfence
ret
Обратите внимание, что xchg
также является эффективным способом сделать seq_cst
хранить на x86-64, обычно более эффективно, чем mov
+ mfence
, который использует g cc. Clang использует xchg
для всего этого (кроме расслабленного магазина).
Забавно, что Clang повторно логизирует 0/1 после xchg в atomic_flag.test_and_set()
, но G CC вместо этого делает это после atomic<bool>
. clang делает странный and al,1
в TAS_bool, который рассматривал бы значения типа 2
как ложные Это кажется совершенно бессмысленным; ABI гарантирует, что bool
в памяти всегда сохраняется как 0
или 1
байт.
Для ARM у нас есть ldrexb
/ strexb
циклов повторных попыток обмена или просто strb
+ dmb ish
для чистого магазина. Либо AArch64 может использовать stlrb wzr, [x0]
для clear
или assign-false для создания хранилища с последовательным выпуском (нулевого регистра) без барьера.