performance x86 sse intrinsics micro-optimization

performance - latencia vs rendimiento en intrínsecos de Intel



x86 sse (1)

Creo que tengo una comprensión decente de la diferencia entre la latencia y el rendimiento, en general. Sin embargo, las implicaciones de la latencia en el rendimiento de la instrucción no me son claras para Intel Intrinsics, particularmente cuando se usan múltiples llamadas intrínsecas secuencialmente (o casi secuencialmente).

Por ejemplo, consideremos:

_mm_cmpestrc

Esto tiene una latencia de 11 y un rendimiento de 7 en un procesador Haswell. Si ejecutara estas instrucciones en un bucle, ¿obtendría un resultado continuo por ciclo después de 11 ciclos? Como esto requeriría la ejecución de 11 instrucciones a la vez, y como tengo un rendimiento de 7, ¿me quedo sin "unidades de ejecución"?

No estoy seguro de cómo utilizar la latencia y el rendimiento más que para obtener una impresión de cuánto tiempo tomará una instrucción relativa a una versión diferente del código.


Para obtener una imagen mucho más completa del rendimiento de la CPU, consulte la guía de microarquitectura y las tablas de instrucciones de Agner Fog . (También sus guías Optimizing C ++ y Optimizing Assembly son excelentes). Ver también otros enlaces en la wiki de la etiqueta x86 , especialmente el manual de optimización de Intel.

Para ver ejemplos de análisis de secuencias cortas de código, consulte

La latencia y el rendimiento para una sola instrucción no son suficientes para obtener una imagen útil para un ciclo que utiliza una combinación de instrucciones vectoriales. Esos números no le dicen qué intrínsecos (instrucciones de asm) compiten entre sí por los recursos de rendimiento (es decir, si necesitan el mismo puerto de ejecución o no). Solo son suficientes para bucles súper simples que, por ejemplo, cargan / hacen una cosa / tienda o, por ejemplo, suman una matriz con _mm_add_ps o _mm_add_epi32 .

Puede usar múltiples acumuladores para obtener más paralelismo de nivel de instrucción , pero solo está utilizando uno intrínseco, por lo que tiene suficiente información para ver que, por ejemplo, las CPU antes de que Skylake solo pueda soportar un rendimiento de un _mm_add_ps por reloj, mientras que SKL puede comenzar dos por ciclo de reloj (rendimiento recíproco de uno por 0.5c). Puede ejecutar ADDPS en sus unidades de ejecución FMA totalmente segmentadas, en lugar de tener una única unidad dedicada de FP-add, de ahí el mejor rendimiento pero peor latencia que Haswell (3c lat, uno por 1c tput).

Como _mm_add_ps tiene una latencia de 4 ciclos en Skylake, eso significa que las operaciones de adición de 8 vector-FP pueden estar en vuelo a la vez. Por lo tanto, necesita 8 acumuladores vectoriales independientes (que se suman unos a otros al final) para exponer tanto paralelismo. (por ejemplo, desenrollar manualmente su ciclo con 8 __m256 sum0, sum1, ... separadas __m256 sum0, sum1, ... desenrollamiento impulsado por el compilador (compilar con -funroll-loops -ffast-math ) a menudo usará el mismo registro, pero la sobrecarga del bucle no fue el problema )

Esos números también dejan fuera la tercera dimensión principal del rendimiento de la CPU de Intel: el rendimiento uop del dominio fusionado. La mayoría de las instrucciones se decodifican en un solo uop, pero algunas decodifican en múltiples uops. (Especialmente las instrucciones de cadena SSE4.2 como el _mm_cmpestrc que mencionas: PCMPESTRI es de 8 uops en Skylake). Incluso si no hay ningún cuello de botella en ningún puerto de ejecución específico, aún puede atascar la capacidad de la interfaz para mantener el núcleo fuera de servicio alimentado con trabajo por hacer. Las CPU Intel Sandybridge-family pueden emitir hasta 4 uops de dominio fusionado por reloj, y en la práctica a menudo pueden acercarse a eso cuando no se producen otros cuellos de botella. (Consulte ¿Se reduce el rendimiento al ejecutar bucles cuyo recuento de uop no es un múltiplo del ancho del procesador? Para algunas pruebas interesantes de mejor rendimiento frontend para diferentes tamaños de bucle). Dado que las instrucciones load / store utilizan puertos de ejecución diferentes a las instrucciones ALU, esto puede ser el cuello de botella cuando los datos están calientes en la memoria caché L1.

Y a menos que mires el asm generado por el compilador, no sabrás cuántas instrucciones MOVDQA adicionales el compilador tuvo que usar para copiar datos entre registros, para evitar el hecho de que sin AVX, la mayoría de las instrucciones reemplazan su primer registro fuente con el resultado. (es decir, destino destructivo). Tampoco sabrá sobre la sobrecarga de bucle de ninguna operación escalar en el bucle.

Creo que tengo una comprensión decente de la diferencia entre la latencia y el rendimiento

Tus conjeturas no parecen tener sentido, así que definitivamente te estás perdiendo algo.

Las CPU se canalizan , al igual que las unidades de ejecución dentro de ellas. Una unidad de ejecución "totalmente interconectada" puede iniciar una nueva operación en cada ciclo (rendimiento = uno por reloj)

  • (recíproco) El rendimiento es la frecuencia con que una operación puede comenzar cuando ninguna dependencia de datos lo obliga a esperar, por ejemplo, uno por cada 7 ciclos para esta instrucción.

  • La latencia es cuánto tardan los resultados de una operación en estar listos, y generalmente solo importa cuando es parte de una cadena de dependencia transportada por bucle.

    Si la siguiente iteración de un ciclo opera independientemente de la anterior, entonces la ejecución fuera de orden puede "ver" lo suficientemente lejos para encontrar el paralelismo de nivel de instrucción entre dos iteraciones y mantenerse ocupado, obstaculizando solo el rendimiento.

(No se ha editado por completo, lo arreglará más tarde).