Las ventajas de utilizar registros/instrucciones de 32 bits en x86-64
A veces, gcc usa un registro de 32 bits, cuando yo esperaría que usara un registro de 64 bits. Por ejemplo el siguiente código C:
unsigned long long
div(unsigned long long a, unsigned long long b){
return a/b;
}
se compila con la opción -O2 para (omitiendo algunas cosas repetitivas):
div:
movq %rdi, %rax
xorl %edx, %edx
divq %rsi
ret
Para la división sin firmar, el registro %rdx
debe ser 0
. Esto se puede lograr mediante xorq %rdx, %rdx
, pero xorl %edx, %edx
parece tener el mismo efecto.
Al menos en mi máquina no hubo ganancia de rendimiento (es decir, aceleración) durante xorl
más de xorq
.
En realidad tengo más de una pregunta:
- ¿Por qué gcc prefiere la versión de 32 bits?
- ¿Por qué gcc se detiene
xorl
y no se usaxorw
? - ¿Hay máquinas para las cuales
xorl
es más rápido quexorq
? - ¿Se debe siempre preferir registros/operaciones de 32 bits, si es posible, en lugar de registros/operaciones de 64 bits?
¿Por qué gcc prefiere la versión de 32 bits?
Principalmente tamaño del código: no se necesita prefijo REX en la codificación del código de máquina.
¿Por qué gcc se detiene
xorl
y no se usaxorw
?
Escribir un registro parcial de 8 o 16 bits no se extiende al resto del registro. ( Solo escribir un registro de 32 bits implícitamente cero se extiende a 64 )
Además, xorw
requiere un prefijo de tamaño de operando para codificar, por lo que tiene el mismo tamaño que xorq
, mayor que xorl
. El tamaño de operando de 32 bits es el predeterminado en el código de máquina x86-64, no se requieren prefijos. (Para la mayoría de las instrucciones; algunas como push
/ pop
y call
/ jmp
tienen un valor predeterminado de 64 bits, incluida la memoria indirecta call [rdi]
= ff 17
con un puntero en la memoria). El tamaño de operando de 8 bits utiliza códigos de operación separados, no prefijos, pero aún así tiene potencialmente penalizaciones de registro parcial.
Consulte también ¿Por qué GCC no utiliza registros parciales? Los registros de 32 bits no se consideran registros parciales, porque al escribirlos siempre se escribe el registro completo de 64 bits. (Y el principal problema es escribir registros parciales, no leerlos después de una escritura de ancho completo).
¿Hay máquinas para las cuales xorl es más rápido que xorq?
Sí, Silvermont/KNL solo reconoce xor
la puesta a cero como un modismo de puesta a cero (romper la dependencia y otras cosas buenas) con un tamaño de operando de 32 bits. Por lo tanto, aunque el tamaño del código sea el mismo, xor %r10d, %r10d
es mucho mejor que xor %r10, %r10
. ( xor
necesita un prefijo REX r10
independientemente del tamaño del operando).
En todas las CPU, el tamaño del código siempre es potencialmente importante para la decodificación y la huella de I-cache (excepto cuando una .p2align
directiva posterior simplemente generaría más relleno si el código anterior es más pequeño 1 ). No hay ninguna desventaja en usar un tamaño de operando de 32 bits para xor-zeroing (o extender implícitamente cero en general en lugar de explícito 2 , incluido el uso de AVX vpxor xmm0,xmm0,xmm0
para poner a cero AVX512 zmm0 ).
La mayoría de las instrucciones tienen la misma velocidad para todos los tamaños de operandos , porque las CPU x86 modernas pueden permitirse el presupuesto de transistores para ALU anchas. Las excepciones incluyen imul r64,r64
que es más lento que imul r32,r32
en las CPU AMD anteriores a Ryzen e Intel Atom , y 64 bits div
es significativamente más lento en todas las CPU. AMD pre-Ryzen es más lento popcnt r64
. Atom/ Silvermont tienen lento shld/shrd r64
vs. r32
La corriente principal de Intel (Skylake, etc.) es más lenta bswap r64
.
¿Se debe siempre preferir registros/operaciones de 32 bits, si es posible, en lugar de registros/operaciones de 64 bits?
Sí, prefiera operaciones de 32 bits al menos por razones de tamaño de código , pero tenga en cuenta que usar r8..r15 en cualquier parte de una instrucción (incluido un modo de direccionamiento) también requerirá un prefijo REX. Entonces, si tiene algunos datos con los que puede usar un tamaño de operando de 32 bits (o punteros a datos de 8/16/32 bits), prefiera mantenerlos en los 8 registros con nombre bajos (e/rax...) en lugar de altos. 8 registros numerados.
Pero no gaste instrucciones adicionales para que esto suceda; Guardar unos pocos bytes de tamaño de código suele ser la consideración menos importante. por ejemplo, simplemente utilícelo r8d
en lugar de guardar/restaurar rbx
para poder usarlo ebx
si necesita un registro adicional que no tenga que conservarse en las llamadas. Usar 32 bits r8d
en lugar de 64 bits r8
no ayudará con el tamaño del código, pero puede ser más rápido para algunas operaciones en algunas CPU (ver arriba).
Esto también se aplica a los casos en los que solo le importan los 16 bits bajos de un registro, pero aún puede ser más eficiente usar una adición de 32 bits en lugar de 16 bits .
Véase también http://agner.org/optimize/ y elx86etiqueta wiki.
Nota al pie 1 : existen casos de uso poco comunes para hacer que las instrucciones sean más largas de lo necesario ( ¿Qué métodos se pueden usar para extender de manera eficiente la longitud de las instrucciones en x86 moderno? )
Para alinear un objetivo de rama posterior sin necesidad de un NOP.
Ajuste del front-end de una microarquitectura específica (es decir, optimización de la decodificación controlando dónde están los límites de las instrucciones). Insertar NOP costaría un ancho de banda frontal adicional y frustraría por completo el propósito.
Los ensambladores no harán esto por usted, y hacerlo a mano requiere mucho tiempo para volver a hacerlo cada vez que cambia algo (y es posible que deba usar .byte
directivas para codificar manualmente la instrucción).
Nota al pie 2 : He encontrado una excepción a la regla de que la extensión cero implícita es al menos tan barata como una operación más amplia: las cargas Haswell/Skylake AVX de 128 bits que se leen mediante una instrucción de 256 bits tienen 1c adicional de almacenamiento. latencia de reenvío frente a ser consumido por una instrucción de 128 bits. (Detalles en un hilo en el foro del blog de Agner Fog ).