Questa domanda è un'estensione di due discussioni che sono emerse di recente nelle risposte a " C ++ vs Fortran per HPC ". Ed è un po 'più una sfida che una domanda ...
Uno degli argomenti più ascoltati a favore di Fortran è che i compilatori sono semplicemente migliori. Poiché la maggior parte dei compilatori C / Fortran condividono lo stesso back-end, il codice generato per programmi semanticamente equivalenti in entrambe le lingue dovrebbe essere identico. Si potrebbe sostenere, tuttavia, che C / Fortran è più / meno facile da ottimizzare per il compilatore.
Così ho deciso di provare un semplice test: ho ottenuto una copia di daxpy.f e daxpy.c e li ho compilati con gfortran / gcc.
Ora daxpy.c è solo una traduzione f2c di daxpy.f (codice generato automaticamente, brutto come diamine), quindi ho preso quel codice e l'ho ripulito un po '(incontra daxpy_c), che sostanzialmente significava riscrivere il ciclo più interno come
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Infine, l'ho riscritto (inserisci daxpy_cvec) usando la sintassi vettoriale di gcc:
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
Si noti che io uso vettori di lunghezza 2 (tutto ciò che SSE2 consente) e che elaboro due vettori alla volta. Questo perché su molte architetture, potremmo avere più unità di moltiplicazione di quanto non abbiamo elementi vettoriali.
Tutti i codici sono stati compilati usando gfortran / gcc versione 4.5 con i flag "-O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-pointer -malign-double -fstrict-aliasing". Sul mio laptop (CPU Intel Core i5, M560, 2,67 GHz) ho ottenuto il seguente output:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
Quindi il codice originale di Fortran impiega poco più di 8,1 secondi, la sua traduzione automatica dura 10,5 secondi, l'implementazione C ingenua lo fa in 7,9 e il codice esplicitamente vettoriale lo fa in 5,6, leggermente meno.
Fortran è leggermente più lento dell'implementazione C ingenua e il 50% più lento dell'implementazione C vettoriale.
Quindi ecco la domanda: sono un programmatore C nativo e quindi sono abbastanza sicuro di aver fatto un buon lavoro su quel codice, ma il codice Fortran è stato toccato l'ultima volta nel 1993 e potrebbe quindi essere un po 'obsoleto. Dato che a Fortran non mi sento a mio agio con la codifica come altri qui, qualcuno può fare un lavoro migliore, cioè più competitivo rispetto a una delle due versioni C?
Inoltre, qualcuno può provare questo test con icc / ifort? La sintassi vettoriale probabilmente non funzionerà, ma sarei curioso di vedere come si comporta l'ingenua versione C lì. Lo stesso vale per chiunque abbia xlc / xlf in giro.
Ho caricato le fonti e un Makefile qui . Per ottenere tempi precisi, impostare CPU_TPS in test.c sul numero di Hz sulla CPU. Se trovi miglioramenti a una qualsiasi delle versioni, pubblicale qui!
Aggiornare:
Ho aggiunto il codice di prova di Stali ai file online e l'ho integrato con una versione C. Ho modificato i programmi per fare 1'000'000 loop su vettori di lunghezza 10'000 per essere coerente con il test precedente (e poiché la mia macchina non è stata in grado di allocare vettori di lunghezza 1'000'000'000, come nell'originale di Stali codice). Dato che i numeri sono ora un po 'più piccoli, ho usato l'opzione -par-threshold:50
per rendere più probabile il parallelismo del compilatore. La versione icc / ifort utilizzata è 12.1.2 20111128 e i risultati sono i seguenti
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
In sintesi, i risultati sono, per tutti gli scopi pratici, identici per entrambe le versioni C e Fortran, ed entrambi i codici si parallelizzano automaticamente. Si noti che i tempi rapidi rispetto al test precedente sono dovuti all'uso dell'aritmetica in virgola mobile a precisione singola!
Aggiornare:
Sebbene non mi piaccia davvero dove sta andando l'onere della prova, ho ricodificato l' esempio di moltiplicazione della matrice di Stali in C e l'ho aggiunto ai file sul web . Ecco i risultati del tripple loop per una e due CPU:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
Si noti che cpu_time
in Fortran si misura il tempo della CPU e non l'ora dell'orologio da parete, quindi ho inserito le chiamate time
per confrontarle per 2 CPU. Non c'è alcuna differenza reale tra i risultati, tranne per il fatto che la versione C fa un po 'meglio su due core.
Ora per il matmul
comando, ovviamente solo in Fortran poiché questo intrinseco non è disponibile in C:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
Wow. È assolutamente terribile. Qualcuno può scoprire cosa sto facendo di sbagliato, o spiegare perché questo intrinseco è ancora in qualche modo una buona cosa?
Non ho aggiunto le dgemm
chiamate al benchmark poiché sono chiamate in libreria alla stessa funzione in Intel MKL.
Per i test futuri, qualcuno può suggerire un esempio noto per essere più lento in C rispetto a Fortran?
Aggiornare
Per verificare l'affermazione matmul
di Stali secondo cui l' intrinseco è "un ordine di grandezza" più veloce del prodotto a matrice esplicita su matrici più piccole, ho modificato il suo codice per moltiplicare matrici di dimensioni 100x100 usando entrambi i metodi, 10'000 volte ciascuno. I risultati, su una e due CPU, sono i seguenti:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
Aggiornare
Grisu ha ragione nel sottolineare che, senza ottimizzazioni, gcc converte le operazioni su numeri complessi in chiamate di funzioni di libreria mentre gfortran le inserisce in alcune istruzioni.
Il compilatore C genererà lo stesso codice compatto se l'opzione -fcx-limited-range
è impostata, ovvero al compilatore viene richiesto di ignorare i potenziali over / under -flow nei valori intermedi. Questa opzione è in qualche modo impostata di default in gfortran e può portare a risultati errati. La forzatura -fno-cx-limited-range
in gfortran non ha cambiato nulla.
Quindi, questo è in realtà un argomento contro l' uso di gfortran per i calcoli numerici: le operazioni su valori complessi possono over / under-flow anche se i risultati corretti sono all'interno dell'intervallo in virgola mobile. Questo è in realtà uno standard Fortran. In gcc, o in C99 in generale, il default è fare le cose rigorosamente (leggi conforme IEEE-754) se non diversamente specificato.
Promemoria: tieni presente che la domanda principale era se i compilatori Fortran producessero un codice migliore rispetto ai compilatori C. Questo non è il luogo per discussioni sui meriti generali di una lingua rispetto a un'altra. Quello a cui sarei davvero interessato è se qualcuno trovasse un modo per convincere gfortran a produrre un daxpy efficiente come quello in C usando la vettorializzazione esplicita in quanto esemplifica i problemi di dover fare affidamento sul compilatore esclusivamente per l'ottimizzazione SIMD, o un caso in cui un compilatore Fortran supera la sua controparte C.