улучшить атомарное чтение из InterlockedCompareExchange () - PullRequest
0 голосов
/ 22 ноября 2018

Предполагается, что архитектура ARM64 или x86-64.

Я хочу убедиться, что эти два эквивалента:

  1. a = _InterlockedCompareExchange64((__int64*)p, 0, 0);
  2. MyBarrier(); a = *(volatile __int64*)p; MyBarrier();

Где MyBarrier() - это барьер памяти (подсказка) уровня компилятора, например __asm__ __volatile__ ("" ::: "memory").Таким образом, метод 2 должен быть быстрее, чем метод 1.

Я слышал, что функции _Interlocked() также подразумевали бы барьер памяти как на уровне компилятора, так и на уровне оборудования.

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

(ps. потому что я думаю, что CPU будет обрабатывать зависимость от данных автоматически, поэтому аппаратный барьер здесь не особо рассматривается.)

Спасибо за любые рекомендации / исправления по этому вопросу.


Вот некоторые тесты для Ivy Bridge (ноутбук i5).

(петли 1E + 006: 27мс ):

; __int64 a = _InterlockedCompareExchange64((__int64*)p, 0, 0);
xor eax, eax
lock cmpxchg QWORD PTR val$[rsp], rbx

(петли 1E + 006: 27мс ):

; __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]

1 Ответ

0 голосов
/ 22 ноября 2018

Чтобы вторая версия была функционально эквивалентной, вам, очевидно, необходимы атомарные 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, и оптимизирующий компилятор должен скомпилировать ее в наиболее оптимизированную версию для вашей архитектуры,и добавьте все необходимые барьеры для предотвращения переупорядочения и обеспечения когерентности кэша.

...