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.
la source
restrict
mot clé qui indique exactement au compilateur: supposer qu'un tableau ne chevauche aucune autre structure de données.Réponses:
La différence dans vos timings semble être due au déroulement manuel du daxpy Fortran à grandes foulées . Les timings suivants s’appliquent au Xeon X5650 à 2,67 GHz, à l’aide de la commande
Compilateurs Intel 11.1
Fortran avec déroulement manuel: 8,7 secondes
Fortran sans déroulement manuel: 5,8 secondes
C sans déroulement manuel: 5,8 secondes
Compilateurs GNU 4.1.2
Fortran avec déroulement manuel: 8.3 sec
Fortran sans déroulement manuel: 13.5 sec
C sans déroulement manuel: 13.6 sec
C avec attributs vectoriels: 5.8 sec
Compilateurs GNU 4.4.5
Fortran avec déroulage manuel: 8.1 sec
Fortran sans déroulage manuel: 7.4 sec
C sans déroulage manuel: 8.5 sec
C avec vecteur atrribué: 5.8 sec
Conclusions
Il est temps de tester des routines plus compliquées comme dgemv et dgemm?
la source
J'arrive en retard à cette soirée, alors il m'est difficile de suivre le va-et-vient de tout ce qui précède. La question est vaste et je pense que si vous êtes intéressé, elle pourrait être scindée en morceaux plus petits. Une chose qui m'a intéressé était simplement la performance de vos
daxpy
variantes, et si Fortran est plus lent que C sur ce code très simple.Sous mon ordinateur portable (Macbook Pro, Intel Core i7, 2,66 GHz), les performances relatives de votre version C vectorisée à la main et de la version Fortran non vectorisée à la main dépendent du compilateur utilisé (avec vos propres options):
Il semble donc que GCC a mieux réussi à vectoriser la boucle dans la branche 4.6 qu'auparavant.
Sur l'ensemble du débat, je pense que l'on peut à peu près écrire du code rapide et optimisé à la fois en C et en Fortran, presque comme dans le langage assembleur. Je ferai cependant remarquer une chose: tout comme un assembleur est plus fastidieux à écrire que C, mais vous donne un contrôle plus fin sur ce qui est exécuté par le CPU, C est plus bas que Fortran. Ainsi, il vous donne plus de contrôle sur les détails, ce qui peut vous aider à optimiser les situations où la syntaxe standard de Fortran (ou ses extensions de fournisseur) peut manquer. Un cas est l'utilisation explicite de types de vecteurs, un autre est la possibilité de spécifier manuellement l'alignement de variables, ce dont Fortran est incapable.
la source
La façon dont j'écrirais AXPY en Fortran est légèrement différente. C'est la traduction exacte du calcul.
m_blas.f90
Appelons maintenant la routine ci-dessus dans un programme.
test.f90
Maintenant compilons et exécutons-le ...
Notez que je n’utilise aucune boucle ou directive OpenMP explicite . Cela serait-il possible en C (c'est-à-dire, pas d'utilisation de boucles et de parallélisation automatique)? Je n'utilise pas C alors je ne sais pas.
la source
icc
également la parallélisation automatique. J'ai ajouté un fichiericctest.c
aux autres sources. Pouvez-vous le compiler avec les mêmes options que celles que vous avez utilisées ci-dessus, l'exécuter et en indiquer les horaires? J'ai dû ajouter une instruction printf à mon code pour éviter que gcc optimise tout. Ceci est juste un rapide bidouillage et j'espère que c'est sans bug!Je pense que la façon dont un compilateur optimise le code pour du matériel moderne n’est pas seulement intéressante. Surtout entre GNU C et GNU Fortran, la génération de code peut être très différente.
Prenons donc un autre exemple pour montrer les différences entre eux.
En utilisant des nombres complexes, le compilateur GNU C génère une surcharge importante pour une opération arithmétique presque très basique sur un nombre complexe. Le compilateur Fortran donne un code bien meilleur. Jetons un coup d'œil au petit exemple suivant dans Fortran:
donne (gfortran -g -o complex.fo -c complex.f95; objdump -d -S complex.fo):
Qui sont 39 octets de code machine. Quand on considère la même chose en C
et jetez un oeil à la sortie (faite de la même manière que ci-dessus), on obtient:
Ce qui sont également du code machine de 39 octets, mais auquel l’étape 57 fait référence, effectue la partie correcte du travail et effectue l’opération souhaitée. Nous avons donc un code machine à 27 octets pour exécuter la multi-opération. La fonction derrière est muldc3 fournie par
libgcc_s.so
et a une empreinte de 1375 octets dans le code machine. Cela ralentit considérablement le code et donne un résultat intéressant lors de l'utilisation d'un profileur.Lorsque nous implémentons les exemples BLAS ci-dessus pour
zaxpy
le même test et que nous le réalisons , le compilateur Fortran devrait donner de meilleurs résultats que le compilateur C.(J’ai utilisé GCC 4.4.3 pour cette expérience, mais j’ai remarqué ce comportement dans lequel un autre GCC est relâché.)
Donc, à mon avis, non seulement nous pensons à la parallélisation et à la vectorisation, mais nous nous devons également de voir comment les éléments de base sont traduits en code assembleur. Si cette traduction donne un code incorrect, l'optimisation ne peut utiliser ces informations qu'en entrée.
la source
complex.c
et de l'ajouter au code en ligne. J'ai dû ajouter toutes les entrées / sorties pour m'assurer que rien n'est optimisé. Je ne reçois un appel que__muldc3
si je ne l'utilise pas-ffast-math
. Avec-O2 -ffast-math
je reçois 9 lignes d'assembleur en ligne. Pouvez-vous confirmer cela?-ffast-math
), vous ne devriez pas utiliser Fortran pour vos calculs à valeurs complexes. Comme je le décris dans la mise à jour de ma question,-ffast-math
ou plus généralement,-fcx-limited-range
force gcc à utiliser les mêmes calculs de plage restreinte non IEEE que ceux standard dans Fortran. Donc, si vous voulez toute la gamme des valeurs complexes et des Infs et NaN corrects, vous ne devriez pas utiliser Fortran ...Gens,
J'ai trouvé cette discussion très intéressante, mais j'ai été surpris de voir que le fait de réorganiser les boucles dans l'exemple de Matmul a changé la donne. Je n'ai pas de compilateur intel disponible sur ma machine actuelle, donc j'utilise gfortran, mais une réécriture des boucles dans le mm_test.f90 à
changé tous les résultats pour ma machine.
Les résultats de la version précédente étaient les suivants:
alors qu'avec les triples boucles réorganisées comme ci-dessus:
Il s’agit de gcc / gfortran 4.7.2 20121109 sur un processeur Intel (R) Core (TM) i7-2600K à 3,40 GHz.
Les drapeaux de compilateur utilisés étaient ceux du Makefile que j'ai obtenu ici ...
la source
Ce ne sont pas les langues qui accélèrent l’exécution du code, bien qu’elles aident. C'est le compilateur, le processeur et le système d'exploitation qui accélèrent l'exécution des codes. Comparer des langues n'est qu'un abus de langage, inutile et dénué de sens. Cela n'a aucun sens, car vous comparez deux variables: le langage et le compilateur. Si un code s'exécute plus rapidement, vous ne savez pas à quel point c'est le langage ou le compilateur. Je ne comprends pas pourquoi la communauté informatique ne comprend tout simplement pas ceci :-(
la source