Cómo determinar el CPE: ciclos por elemento

¿Cómo determino el CPE de un programa? Por ejemplo, tengo este código ensamblador para un bucle:

# inner4: data_t = float
# udata in %rbx, vdata in %rax, limit in %rcx,
# i in %rdx, sum in %xmm1
1 .L87:                                   # loop:
2   movss  (%rbx,%rdx,4), %xmm0           #  Get udata[i]
3   mulss  (%rax,%rdx,4), %xmm0           #  Multiply by vdata[i]
4   addss  %xmm0, %xmm1                   #  Add to sum
5   addq  $1, %rdx                        #  Increment i
6   cmpq  %rcx, %rdx                      #  Compare i:limit
7   jl .L87                               #  If <, goto loop

Tengo que encontrar el límite inferior del CPE determinado por la ruta crítica usando el tipo de datos flotante. Creo que la ruta crítica se referiría a la ruta más lenta posible, y por lo tanto sería aquella en la que el programa tiene que ejecutar la instrucción mulss porque ocupa la mayor cantidad de ciclos de reloj.

Sin embargo, no parece haber una forma clara de determinar el CPE. Si una instrucción toma dos ciclos de reloj y otra toma uno, ¿puede comenzar la última después del primer ciclo de reloj de la primera? Cualquier ayuda sería apreciada. Gracias

preguntado el 02 de mayo de 12 a las 19:05

Esa es una pregunta difícil de responder, porque los ciclos requeridos dependen en gran medida de la CPU de destino. Diferentes cpus pueden requerir una cantidad diferente de ciclos para una sola instrucción. Algunas CPU pueden ejecutar algunas de las instrucciones desordenadas en canalizaciones paralelas (como la línea 5+6 mientras que la 4 está esperando el resultado de la 3), mientras que otras no. -

2 Respuestas

Si quieres saber cuánto tiempo necesita, debes medirlo. Ejecute el bucle unas 10^10 veces, tome el tiempo que necesita y multiplíquelo por la frecuencia del reloj. Obtiene el recuento total de ciclos, se divide por 10 ^ 10 para obtener el número de ciclos de reloj por iteración de ciclo.

Una predicción teórica del tiempo de ejecución casi nunca ser correcto (y la mayoría de las veces bajo) porque hay numerosos efectos que determinan la velocidad:

  • Tubería (puede haber fácilmente alrededor de 20 etapas en la tubería)
  • Ejecución superescalar (hasta 5 instrucciones en paralelo, cmp y jl puede estar fusionado)
  • Decodificación a µOps y reordenación
  • Las latencias de Cachés o Memoria
  • El rendimiento de las instrucciones (hay suficientes puertos de ejecución libres)
  • Las latencias de las instrucciones
  • Conflictos bancarios, problemas de aliasing y cosas más esotéricas

Dependiendo de la CPU y siempre que todos los accesos a la memoria lleguen al caché L1, creo que el bucle debería necesitar al menos 3 ciclos de reloj por iteración, porque la cadena de dependencia más larga tiene 3 elementos. En una CPU más antigua con más lento mulss or addss instrucción el tiempo necesario aumenta.

Si realmente está interesado en acelerar el código y no solo algunas observaciones teóricas, debe vectorizarlo. Puede aumentar el rendimiento en un factor de 4-8 con algo como

.L87:                               # loop:
vmovdqa (%rbx,%rdx,4), %ymm0        #  Get udata[i]..udata[i+7]
vmulps  (%rax,%rdx,4), %ymm0, %ymm0 #  Multiply by vdata[i]..vdata[i+7]
vaddps  %ymm0, %ymm1, %ymm1         #  Add to sum
addq    $8, %rdx                    #  Increment i
cmpq    %rcx, %rdx                  #  Compare i:limit
jl .L87                             #  If <, goto loop

Debe agregar horizontalmente los 8 elementos después de eso y, por supuesto, asegurarse de que la alineación sea 32 y el contador de bucle divisible por 8.

contestado el 03 de mayo de 12 a las 00:05

Si está ejecutando una CPU Intel, puede encontrar buena documentación sobre la latencia de las instrucciones y el rendimiento para varias CPU. Aquí está el enlace:

Manual de referencia de optimización de arquitecturas Intel® 64 e IA-32

contestado el 02 de mayo de 12 a las 23:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.