¿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?