Supongamos que estamos intentando utilizar el tsc para la supervisión del rendimiento y queremos evitar el reordenamiento de las instrucciones.Diferencia entre rdtscp, rdtsc: memoria y cpuid/rdtsc?
Estas son nuestras opciones:
1:rdtscp
es una llamada de serialización. Evita el reordenamiento de la llamada a rdtscp.
__asm__ __volatile__("rdtscp; " // serializing read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc variable
:
: "%rcx", "%rdx"); // rcx and rdx are clobbered
Sin embargo, rdtscp
sólo está disponible en los nuevos CPUs. Entonces, en este caso, tenemos que usar rdtsc
. Pero rdtsc
no se serializa, por lo que su uso por sí solo no evitará que la CPU vuelva a ordenarlo.
así que podemos usar cualquiera de estas dos opciones para evitar la reordenación:
2: Este es un llamado a cpuid
y luego rdtsc
. cpuid
es una llamada de serialización.
volatile int dont_remove __attribute__((unused)); // volatile to stop optimizing
unsigned tmp;
__cpuid(0, tmp, tmp, tmp, tmp); // cpuid is a serialising call
dont_remove = tmp; // prevent optimizing out cpuid
__asm__ __volatile__("rdtsc; " // read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc
:
: "%rcx", "%rdx"); // rcx and rdx are clobbered
3: Este es un llamado a rdtsc
con memory
en la lista clobber, lo que evita la reordenación
__asm__ __volatile__("rdtsc; " // read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc
:
: "%rcx", "%rdx", "memory"); // rcx and rdx are clobbered
// memory to prevent reordering
Mi entendimiento para la tercera opción es la siguiente:
Hacer el call __volatile__
impide que el optimizador elimine el asm o lo mueva a través de las instrucciones que puedan necesitar los resultados (o cambiar las entradas) del asm. Sin embargo, aún podría moverlo con respecto a operaciones no relacionadas. Entonces __volatile__
no es suficiente.
Informe a todo el compilador de memoria está siendo clobbered: : "memory")
. El "memory"
clobber significa que GCC no puede hacer ninguna suposición acerca de que el contenido de la memoria permanezca igual en toda la memoria y, por lo tanto, no se reordenará a su alrededor.
Así que mis preguntas son:
- 1: ¿Es mi comprensión de
__volatile__
y"memory"
correcta? - 2: ¿Las dos segundas llamadas hacen lo mismo?
- 3: El uso de
"memory"
parece mucho más simple que utilizar otra instrucción de serialización. ¿Por qué alguien usaría la tercera opción sobre la segunda opción?
Parece que confunde el reordenamiento de las instrucciones generadas por el compilador, lo cual puede evitar usando 'volátil' y' memoria' y reordenando las instrucciones ejecutadas por el procesador (aka _out of order execution_), que puede evitar al usar ' cpuid'. – hirschhornsalz
@hirschhornsalz pero no tener 'memoria' en la lista de clobber impide que el procesador vuelva a ordenar las instrucciones? ¿La 'memoria' no actúa como una valla de memoria? –
o tal vez la 'memoria' en la lista de clobber solo se emite a gcc, y el código de máquina resultante no expone esto al procesador? –