Sigo viendo que la gente afirma que la instrucción MOV puede ser gratuita en x86, debido al cambio de nombre del registro.
Por mi vida, no puedo verificar esto en un solo caso de prueba. Cada caso de prueba que intento lo desacredita.
Por ejemplo, aquí está el código que estoy compilando con Visual C++:
#include <limits.h>
#include <stdio.h>
#include <time.h>
int main(void)
{
unsigned int k, l, j;
clock_t tstart = clock();
for (k = 0, j = 0, l = 0; j < UINT_MAX; ++j)
{
++k;
k = j; // <-- comment out this line to remove the MOV instruction
l += j;
}
fprintf(stderr, "%d ms\n", (int)((clock() - tstart) * 1000 / CLOCKS_PER_SEC));
fflush(stderr);
return (int)(k + j + l);
}
Esto produce el siguiente código ensamblador para el ciclo (siéntete libre de producirlo como quieras; obviamente no necesitas Visual C++):
LOOP:
add edi,esi
mov ebx,esi
inc esi
cmp esi,FFFFFFFFh
jc LOOP
Ahora ejecuto este programa varias veces y observo una diferencia bastante consistente del 2% cuando se elimina la instrucción MOV:
Without MOV With MOV
1303 ms 1358 ms
1324 ms 1363 ms
1310 ms 1345 ms
1304 ms 1343 ms
1309 ms 1334 ms
1312 ms 1336 ms
1320 ms 1311 ms
1302 ms 1350 ms
1319 ms 1339 ms
1324 ms 1338 ms
Entonces, ¿qué da? ¿Por qué el MOV no es “gratis”? ¿Es este bucle demasiado complicado para x86?
Hay un soltero ejemplo por ahí que puede demostrar que MOV es libre como la gente afirma?
Si es así, ¿qué es? Y si no, ¿por qué todos siguen afirmando que MOV es gratis?
La “gratuidad” tiene que ver con la latencia, que no está midiendo aquí. Además, el 2% de eso es significativamente menos que un ciclo, por lo que solo se debe a “efectos extraños”
– harold
24 mayo 2017 a las 22:19
Bueno, ¿qué significa “totalmente eliminado”? Claramente, no se puede eliminar antes de decodificar, porque aún no se sabe qué es. Como era de esperar, el truco de cambio de nombre puede, en el mejor de los casos, eliminar el mov durante el cambio de nombre y luego ni siquiera siempre. Solo por estar ahí, el mov no puede ser enteramente gratis.
– harold
24 mayo 2017 a las 22:35
Agregaste un 25 % más de instrucciones, pero es solo un 2 % más lento. No puede explicar eso con “parece que no hay eliminación de MOV”. Una diferencia del 2% requiere otra explicación, como que el núcleo se calienta demasiado y se ralentiza.
-Hans Passant
24 mayo 2017 a las 22:59
El cambio de nombre de registro elimina efectivamente el MOV del back-end, lo que significa que consta de 0 µops, no consume un puerto de ejecución y tiene 0 latencia. Sin embargo, la instrucción en sí todavía tiene que ser decodificada, lo cual no es gratis. Además, ocupa espacio en el código, lo que significa espacio en el caché. Así que no, un MOV nunca es realmente gratis, porque hay costos en el front-end, pero a menudo es efectivamente free en el contexto de un bloque de código más grande que está realizando alguna operación significativa. Una diferencia del 2% en la velocidad de ejecución es claramente lejos menos de un ciclo, como cabría esperar ingenuamente.
– Cody gris
♦
26 de mayo de 2017 a las 9:44
@CodyGray: un MOV eliminado ocupa espacio en el ROB hasta que se retira (lo mismo que una instrucción xor-zeroing o incluso un NOP), en hardware Intel (sin predicciones erróneas de rama,
uops_retired.retire_slots
coincidirá casi exactamenteuops_issued.any
). Mi modelo mental es que ingresan al ROB (dominio fusionado) en un estado listo para retirar ya ejecutado, con cero uops de dominio no fusionado emitidos en el RS (programador). Presumiblemente, hay algo no trivial en no tener una opción para retirarse para recibir una instrucción, tal vez algo en actualizar RIP o simplemente revertir las especulaciones erróneas…– Peter Cordes
28 de mayo de 2017 a las 3:59