Cette question est une extension de deux discussions qui ont récemment été abordées dans les réponses à " C ++ vs Fortran pour HPC ". Et c'est un peu plus un défi qu'une question ...
L'un des arguments les plus souvent entendus en faveur de Fortran est que les compilateurs sont simplement meilleurs. Étant donné que la plupart des compilateurs C / Fortran partagent le même back-end, le code généré pour les programmes sémantiquement équivalents dans les deux langues doit être identique. On pourrait toutefois affirmer que C / Fortran est plus / moins facile à optimiser pour le compilateur.
J'ai donc décidé d'essayer un test simple: j'ai reçu une copie de daxpy.f et daxpy.c et les ai compilés avec gfortran / gcc.
Maintenant, daxpy.c n’est qu’une traduction f2c de daxpy.f (code généré automatiquement, moche comme heck), j’ai donc pris ce code et l’a nettoyé un peu (meet daxpy_c), ce qui signifiait essentiellement réécrire la boucle la plus intérieure
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Enfin, je l'ai réécrit (entrez daxpy_cvec) en utilisant la syntaxe vectorielle de 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];
Notez que j'utilise des vecteurs de longueur 2 (c'est tout ce que permet SSE2) et que je traite deux vecteurs à la fois. En effet, sur de nombreuses architectures, nous pouvons avoir plus d'unités de multiplication que d'éléments vectoriels.
Tous les codes ont été compilés avec la version 4.5 de gfortran / gcc avec les indicateurs "-O3 -Wall -msse2 -march = native -fast-math -fomit-frame-pointer -malign-double -fstrict-aliasing". Sur mon ordinateur portable (processeur Intel Core i5, M560, 2,67 GHz), j'ai la sortie suivante:
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.
Donc, le code Fortran original prend un peu plus de 8,1 secondes, sa traduction automatique prend 10,5 secondes, l'implémentation C naïve le fait en 7.9 et le code explicitement vectorisé le fait en 5.6, légèrement moins.
Fortran est légèrement plus lent que l'implémentation naïve du C et 50% moins rapide que l'implémentation du C vectorisé.
Alors voici la question: je suis un programmeur C natif et je suis donc plutôt confiant d'avoir fait du bon travail sur ce code, mais le code Fortran a été touché pour la dernière fois en 1993 et pourrait donc être un peu obsolète. Puisque je ne me sens pas aussi à l'aise de coder en Fortran que d'autres ici, est-ce que quelqu'un peut faire un meilleur travail, c'est-à-dire être plus compétitif que n'importe laquelle des deux versions C?
De plus, quelqu'un peut-il essayer ce test avec icc / ifort? La syntaxe vectorielle ne fonctionnera probablement pas, mais je serais curieux de voir comment la version naïve du C se comporte ici. Il en va de même pour les personnes avec xlc / xlf qui traînent.
J'ai téléchargé les sources et un Makefile ici . Pour obtenir des synchronisations précises, définissez CPU_TPS dans test.c sur le nombre de Hz de votre CPU. Si vous trouvez des améliorations dans les versions, merci de les poster ici!
Mise à jour:
J'ai ajouté le code de test de stali aux fichiers en ligne et l'ai complété avec une version C. J'ai modifié les programmes pour faire 1'000'000 boucles sur des vecteurs de longueur 10'000 afin d'être cohérents avec le test précédent (et parce que ma machine ne pouvait pas affecter des vecteurs de longueur 1'000'000'000, comme dans l'original de stali code). Puisque les nombres sont maintenant un peu plus petits, j'ai utilisé l'option -par-threshold:50
pour rendre le compilateur plus susceptible de se mettre en parallèle. La version icc / ifort utilisée est la 12.1.2 20111128 et les résultats sont les suivants
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
En résumé, les résultats sont, à toutes fins pratiques, identiques pour les versions C et Fortran, et les deux codes se parallélisent automagiquement. Notez que les temps rapides comparés au test précédent sont dus à l'utilisation de l'arithmétique à virgule flottante simple précision!
Mise à jour:
Bien que je n'aime pas trop le fardeau de la preuve ici, j'ai recodifié l' exemple de multiplication de matrice de stali en C et l'a ajouté aux fichiers sur le Web . Voici les résultats de la boucle triple pour un et deux processeurs:
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
Notez que cpu_time
dans les mesures Fortran, le temps de calcul et pas l’horloge murale, c’est pourquoi j’ai encapsulé les appels time
pour les comparer entre deux processeurs. Il n'y a pas de réelle différence entre les résultats, sauf que la version C fait un peu mieux sur deux cœurs.
Maintenant pour la matmul
commande, bien sûr seulement en Fortran car cet élément intrinsèque n’est pas disponible en 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
Sensationnel. C'est absolument terrible. Quelqu'un peut-il savoir ce que je fais de mal ou expliquer pourquoi cet élément intrinsèque est encore en quelque sorte une bonne chose?
Je n'ai pas ajouté les dgemm
appels à la référence car il s'agit d'appels de bibliothèque ayant la même fonction dans Intel MKL.
Pour les prochains tests, quelqu'un peut-il suggérer un exemple connu pour être plus lent en C que dans Fortran?
Mise à jour
Pour vérifier l'affirmation de Stali selon laquelle l' matmul
intrinsèque est "un ordre de grandeur" plus rapidement que le produit matriciel explicite sur des matrices plus petites, j'ai modifié son propre code pour multiplier les matrices de taille 100x100 en utilisant les deux méthodes, 10 000 fois chacune. Les résultats, sur un et deux processeurs, sont les suivants:
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
Mise à jour
Grisu a raison de souligner que, sans optimisation, gcc convertit les opérations sur les nombres complexes en appels de fonctions de bibliothèque, tandis que gfortran les insère dans quelques instructions.
Le compilateur C générera le même code compact si l'option -fcx-limited-range
est définie, c'est-à-dire que le compilateur est invité à ignorer les potentiels de dépassement / dépassement de capacité dans les valeurs intermédiaires. Cette option est en quelque sorte définie par défaut dans gfortran et peut conduire à des résultats incorrects. Forcer -fno-cx-limited-range
à Gfortran n'a rien changé.
Donc, c’est vraiment un argument contre l’ utilisation de gfortran pour les calculs numériques: Les opérations sur des valeurs complexes peuvent dépasser / dépasser le flux même si les résultats corrects se situent dans la plage de virgule flottante. C'est en fait une norme Fortran. Dans gcc, ou dans C99 en général, le comportement par défaut consiste à agir strictement (lire conforme à la norme IEEE-754), sauf indication contraire.
Rappel: Veuillez garder à l’esprit que la question principale était de savoir si les compilateurs Fortran produisaient un meilleur code que les compilateurs C. Ce n’est pas le lieu de discussions sur les mérites généraux d’une langue par rapport à une autre. Ce qui m'intéresserait vraiment, c’est de savoir si quelqu'un peut trouver un moyen de convaincre gfortran de produire un daxpy aussi efficace que celui en C utilisant la vectorisation explicite, car cela illustre bien le problème du recours exclusif au compilateur pour l'optimisation SIMD, ou encore cas dans lequel un compilateur Fortran surpasse sa contrepartie C.