Предполагается, что архитектура ARM64 или x86-64.
Я хочу убедиться, что эти два эквивалентны:
a = _InterlockedCompareExchange64((__int64*)p, 0, 0);
MyBarrier(); a = *(volatile __int64*)p; MyBarrier();
куда MyBarrier()
это барьер памяти (подсказка) уровня компилятора, например __asm__ __volatile__ ("" ::: "memory")
,
Таким образом, метод 2 должен быть быстрее, чем метод 1.
я слышал, что _Interlocked()
Функции также подразумевают барьер памяти как на уровне компилятора, так и на уровне оборудования.
Я слышал, что считанные (правильно выровненные) внутренние данные являются атомарными на этих архитектурах, но я не уверен, что метод 2 может быть широко использован?
(ps. потому что я думаю, что CPU будет обрабатывать зависимость от данных автоматически, поэтому аппаратный барьер здесь не особо рассматривается.)
Спасибо за любой консультировать/коррекция на этом.
Вот несколько тестов на Ivy Bridge (ноутбук i5).
(Петли 1E + 006: 27ms):
; __int64 a = _InterlockedCompareExchange64((__int64*)p, 0, 0);
xor eax, eax
lock cmpxchg QWORD PTR val$[rsp], rbx
(Петли 1E + 006: 27ms):
; __faststorefence(); __int64 a = *(volatile __int64*)p;
lock or DWORD PTR [rsp], 0
mov rcx, QWORD PTR val$[rsp]
(Петли 1E + 006: 7мс):
; _mm_sfence(); __int64 a = *(volatile __int64*)p;
sfence
mov rcx, QWORD PTR val$[rsp]
(Петли 1E + 006: 1.26ms, не синхронизируется?)
; __int64 a = *(volatile __int64*)p;
mov rcx, QWORD PTR val$[rsp]
Чтобы вторая версия была функционально эквивалентной, вам, очевидно, нужны атомарные 64-битные операции чтения, что верно для вашей платформы.
Тем не мение, _MemoryBarrier()
не является «подсказкой компилятору». _MemoryBarrier()
на x86 предотвращает переупорядочение компилятора и процессора, а также обеспечивает глобальную видимость после записи. Вам также, вероятно, нужно только первое _MemoryBarrier()
второй можно заменить на _ReadWriteBarrier()
если a
также является разделяемой переменной, но вам это даже не нужно, так как вы читаете через изменчивый указатель, который предотвратит переупорядочение компилятора в MSVC.
Когда вы создаете эту замену, вы в основном получаете тот же результат:
// a = _InterlockedCompareExchange64((__int64*)&val, 0, 0);
xor eax, eax
lock cmpxchg QWORD PTR __int64 val, r8 ; val
// _MemoryBarrier(); a = *(volatile __int64*)&val;
lock or DWORD PTR [rsp], r8d
mov rax, QWORD PTR __int64 val ; val
Выполнение этих двух циклов на моем ноутбуке i7 Ivy Bridge дает одинаковые результаты в пределах 2-3%.
Однако с двумя барьерами памяти «оптимизированная версия» на самом деле примерно в 2 раза медленнее.
Итак, лучший вопрос: Почему вы используете _InterlockedCompareExchange64
совсем? Если вам нужен атомарный доступ к переменной, используйте std::atomic
и оптимизирующий компилятор должен скомпилировать его в наиболее оптимизированную версию для вашей архитектуры и добавить все необходимые барьеры для предотвращения переупорядочения и обеспечения согласованности кэша.
Других решений пока нет …