Estoy escribiendo un código matemático que necesita multiplicar grandes números rápidamente. Se divide en multiplicaciones de una matriz de enteros con un solo entero. En C++ esto se parece a esto (el de signo):Optimización del ensamblador x64 MUL loop
void muladd(unsigned* r, const unsigned* a, unsigned len, unsigned b) {
unsigned __int64 of = 0; // overflow
unsigned i = 0; // loop variable
while (i < len) {
of += (unsigned __int64)a[i] * b + r[i];
r[i] = (unsigned)of;
of >>= 32;
++i;
}
r[i] = (unsigned)of; // save overflow
}
Desenrollé este bucle de forma manual, lo convirtieron a 64 bits y trabajaron en la salida asm compilador para optimizar aún más. El bucle principal asm ahora se ve así:
mov rax, rdi ; rdi = b
mul QWORD PTR [rbx+r10*8-64] ; rdx:rax = a[i] * b; r10 = i
mov rsi, QWORD PTR [r14+r10*8-64] ; r14 = r; rsi = r[i]
add rax, rsi
adc rdx, 0
add rax, r11 ; r11 = of (low part)
adc rdx, 0
mov QWORD PTR [r14+r10*8-64], rax ; save result
mov r11, rdx
; this repeats itself 8 times with different offsets
Cuando este punto de referencia, encuentro que se tarda unos 6,3 ciclos en avarage_nus por la multiplicación en mi Core 2 Quad.
Mi pregunta es: ¿puedo acelerar esto de alguna manera? Desafortunadamente, no veo forma de evitar una de las adiciones y la multiplicación siempre necesita RDX: RAX, así que necesito mover los datos y no puedo "multiplicar en paralelo".
¿Alguna idea a alguien?
Actualización: Después de algunas pruebas más, me las he arreglado para llevar la velocidad hasta alrededor de 5,4 ciclos por 64 bits MUL (que incluye todos los complementos, se mueven y los gastos generales de bucle). Supongo que esto es lo mejor que puedes obtener en un Core2, ya que el Core2 no tiene una instrucción MUL muy rápida: tiene un rendimiento de 3 y una latencia de 6 (respectivamente 7) ciclos. Sandy Bridge será mucho mejor con un rendimiento de 1 y una latencia de 3 (resp 4) ciclos.
En cuanto al número mucho menor para GMP: lo obtuve de su código fuente y me parece que es un número teórico. Pero lo que es seguro es que es un número que se calculó para una CPU AMD K9. Y por lo que he leído, los AMD tienen una unidad MUL más rápida que los chips Intel (más antiguos).
Es posible que desee echar un vistazo a algunas de las rutinas de ensamblaje en GMP. Tienen una función que hace exactamente esto y está escrita en ensamblaje para la mayoría de los procesadores, incluido x64. – Mysticial
GMP realmente tiene un buen soporte para un mul_basecase rápido y aparentemente les lleva unos 2.35 ciclos por MUL, muy agradable. Si lo entendí correctamente, multiplican dos vectores intercalados, lo que parece mantener bajas las dependencias y mejorar el manejo del desbordamiento. – cxxl