Scrivo codice matematico che deve moltiplicare rapidamente i numeri grandi. Si riduce alle moltiplicazioni di un array di numeri interi con un singolo intero. In C++ questa si presenta così (su di unsigned):Ottimizzazione dell'assembler x64 Ciclo MUL
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
}
ho srotolate questo ciclo manualmente, convertito a 64 bit e ha lavorato sull'uscita .asm compilatore per ottimizzare ulteriormente. Il ciclo .asm principale appare come segue:
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
Quando ho benchmark questo, trovo che ci vogliono circa 6,3 cicli in media di marea per la moltiplicazione sulla mia Core2 Quad.
La mia domanda è: posso accelerare in qualche modo? Sfortunatamente, non vedo alcun modo per evitare una delle aggiunte e la moltiplicazione ha sempre bisogno di RDX: RAX, quindi ho bisogno di spostare i dati e non posso "moltiplicare in parallelo".
Qualche idea a qualcuno?
Aggiornamento: Dopo un po 'di più test, sono riuscito a portare la velocità fino a circa 5,4 cicli al 64-bit MUL (che include tutti aggiungere, spostare e spese generali loop). Immagino che questo sia il meglio che puoi ottenere su un Core2, dal momento che il Core2 non ha un'istruzione MUL molto veloce: ha un throughput di 3 e una latenza di 6 (o 7) cicli. Sandy Bridge sarà molto meglio con un throughput di 1 e una latenza di 3 (o 4) cicli.
Per quanto riguarda il numero molto inferiore per GMP: l'ho ottenuto dal loro codice sorgente e mi sembra che sia un numero teorico. Ma quello che è sicuro è che è un numero che è stato calcolato per una CPU AMD K9. E da quello che ho letto ho scoperto che le AMD hanno un'unità MUL più veloce rispetto ai (vecchi) chip Intel.
Si potrebbe voler dare un'occhiata ad alcune delle routine di assemblaggio in GMP. Hanno una funzione che fa esattamente questo ed è scritta in assembly per la maggior parte dei processori incluso x64. – Mysticial
GMP ha davvero un buon supporto per un mul_basecase veloce e apparentemente impiega circa 2.35 cicli per MUL, molto bello. Se ho capito bene, moltiplicano due vettori intercalati, che sembra mantenere basse le dipendenze e migliorare la gestione del trabocco. – cxxl