¿Debería std :: atomic <int *> :: load estar haciendo un bucle de comparación e intercambio?

Resumen: Había esperado questd::atomic<int*>::load constd::memory_order_relaxed estaría cerca del rendimiento de solo cargar un puntero directamente, al menos cuando el valor cargado rara vez cambia. Vi un rendimiento mucho peor para la carga atómica que una carga normal en Visual Studio C ++ 2012, así que decidí investigar. Resulta que la carga atómica se implementa como uncomparar y cambiar bucle, que sospecho que no es la implementación más rápida posible.

Pregunta: Hay alguna razon questd::atomic<int*>::load necesita hacer un bucle de comparar y cambiar?

Fondo: Creo que MSVC ++ 2012 está haciendo un bucle de comparación e intercambio en la carga atómica de un puntero basado en este programa de prueba:

#include <atomic>
#include <iostream>

template<class T>
__declspec(noinline) T loadRelaxed(const std::atomic<T>& t) {
  return t.load(std::memory_order_relaxed);
}

int main() {
  int i = 42;
  char c = 42;
  std::atomic<int*> ptr(&i);
  std::atomic<int> integer;
  std::atomic<char> character;
  std::cout
    << *loadRelaxed(ptr) << ' '
    << loadRelaxed(integer) << ' '
    << loadRelaxed(character) << std::endl;
  return 0;
}

Estoy usando un__declspec(noinline) Función para aislar las instrucciones de montaje relacionadas con la carga atómica. Hice un nuevo proyecto MSVC ++ 2012, agregué una plataforma x64, seleccioné la configuración de lanzamiento, ejecuté el programa en el depurador y observé el desensamblaje. Resulta que ambosstd::atomic<char> ystd::atomic<int> Los parámetros terminan dando la misma llamada aloadRelaxed<int> - Esto debe ser algo que hizo el optimizador. Aquí está el desmontaje de las dos instancias de LoadRelaxed que se llaman:

loadRelaxed<int * __ptr64>

000000013F4B1790  prefetchw   [rcx]  
000000013F4B1793  mov         rax,qword ptr [rcx]  
000000013F4B1796  mov         rdx,rax  
000000013F4B1799  lock cmpxchg qword ptr [rcx],rdx  
000000013F4B179E  jne         loadRelaxed<int * __ptr64>+6h (013F4B1796h)  

loadRelaxed<int>

000000013F3F1940  prefetchw   [rcx]  
000000013F3F1943  mov         eax,dword ptr [rcx]  
000000013F3F1945  mov         edx,eax  
000000013F3F1947  lock cmpxchg dword ptr [rcx],edx  
000000013F3F194B  jne         loadRelaxed<int>+5h (013F3F1945h)  

La instrucciónlock cmpxchg es atómicocomparar y cambiar y vemos aquí que el código para cargar atómicamente unachar, unint o unint* es un bucle de comparación e intercambio. También construí este código para x86 de 32 bits y esa implementación aún se basa enlock cmpxchg.

Pregunta: Hay alguna razon questd::atomic<int*>::load necesita hacer un bucle de comparar y cambiar?

Respuestas a la pregunta(1)

Su respuesta a la pregunta