Las ventajas de utilizar registros/instrucciones de 32 bits en x86-64

Resuelto ead asked hace 8 años • 2 respuestas

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 %rdxdebe ser 0. Esto se puede lograr mediante xorq %rdx, %rdx, pero xorl %edx, %edxparece tener el mismo efecto.

Al menos en mi máquina no hubo ganancia de rendimiento (es decir, aceleración) durante xorlmás de xorq.

En realidad tengo más de una pregunta:

  1. ¿Por qué gcc prefiere la versión de 32 bits?
  2. ¿Por qué gcc se detiene xorly no se usa xorw?
  3. ¿Hay máquinas para las cuales xorles más rápido que xorq?
  4. ¿Se debe siempre preferir registros/operaciones de 32 bits, si es posible, en lugar de registros/operaciones de 64 bits?
ead avatar Jul 11 '16 16:07 ead
Aceptado

¿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 xorly no se usa xorw?

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, xorwrequiere 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/ popy call/ jmptienen un valor predeterminado de 64 bits, incluida la memoria indirecta call [rdi]= ff 17con 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 xorla 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, %r10des mucho mejor que xor %r10, %r10. ( xornecesita un prefijo REX r10independientemente 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 .p2aligndirectiva 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,xmm0para 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,r64que es más lento que imul r32,r32en las CPU AMD anteriores a Ryzen e Intel Atom , y 64 bits dives 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. r32La 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 r8den lugar de guardar/restaurar rbxpara poder usarlo ebxsi necesita un registro adicional que no tenga que conservarse en las llamadas. Usar 32 bits r8den lugar de 64 bits r8no 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 .bytedirectivas 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 ).

Peter Cordes avatar Jul 11 '2016 19:07 Peter Cordes