Les compilateurs Fortran génèrent-ils vraiment du code plus rapidement que les compilateurs C?

17

Quand j'étudiais à l'université, j'ai souvent entendu l'idée que les compilateurs Fortran produisaient du code plus rapide que les compilateurs C pour un programme équivalent.

Le raisonnement clé était le suivant: un compilateur Fortran émet en moyenne 1,1 instruction de processeur par ligne de code, tandis qu'un compilateur C émet en moyenne 1,6 instruction de processeur par ligne de code - je ne me souviens pas des chiffres exacts, mais le L'idée était que les compilateurs C émettaient sensiblement plus de code machine et produisaient donc des programmes plus lents.

Quelle est la validité d'une telle comparaison? Peut-on dire que les compilateurs Fortran produisent des programmes plus rapides que les compilateurs C ou vice versa et pourquoi cette différence existe-t-elle?

acéré
la source
19
Cela peut simplement signifier que les programmes Fortran sont plus verbeux que C ... Une comparaison significative ne peut être faite qu'en implémentant la même fonctionnalité dans les deux langues et en comparant le code machine résultant (taille et vitesse).
Péter Török
De plus, le code généré prend-il en charge l'exécution parallèle?
@ Péter Török, cela signifie simplement que, disons, BLAS et LAPACK dans Fortran fonctionnaient bien mieux que n'importe lequel de leurs ports C / C ++. Maintenant, l'écart se réduit rapidement.
SK-logic
6
Vous ne pouvez affirmer qu'un seul compilateur produit un code plus rapide si vous avez un programme 100% équivalent dans les deux langues, écrit par des experts qui connaissent leurs compilateurs et qui peuvent rendre compte des performances.
Falcon
L'ancien Fortran ne supportait pas la récursivité et n'avait donc pas nécessairement à pousser les arguments d'appel de fonction sur la pile car il y aurait un espace alloué statiquement pour les arguments de chaque fonction. C'est l'une des raisons pour lesquelles cela aurait pu être plus rapide. Je suppose que vous pouvez trouver une réponse plus complète ici: amazon.com/Programming-Language-Pragmatics-Third-Edition/dp/…
Pedro Rolo

Réponses:

36

L'IIRC, l'une des principales raisons pour lesquelles Fortran serait plus rapide, est l'absence d' alias de pointeur , afin qu'ils puissent utiliser des optimisations que les compilateurs C ne peuvent pas utiliser:

Dans FORTRAN, les arguments de fonction peuvent ne pas se alias et le compilateur suppose qu'ils ne le font pas. Cela permet une excellente optimisation et est l'une des principales raisons de la réputation de FORTRAN en tant que langage rapide. (Notez que l'aliasing peut toujours se produire dans une fonction FORTRAN. Par exemple, si A est un tableau et i et j sont des indices qui ont la même valeur, alors A [i] et A [j] sont deux noms différents pour le même emplacement de mémoire. Heureusement, puisque le tableau de base doit avoir le même nom, une analyse d'index peut être effectuée pour déterminer les cas où A [i] et A [j] ne peuvent pas alias.)

Mais je suis d'accord avec les autres ici: Comparer le nombre moyen d'instructions d'assembleur générées pour une ligne de code est un non-sens complet. Par exemple, un noyau x86 moderne peut exécuter deux instructions en parallèle s'il n'accède pas aux mêmes registres. Ainsi, vous pouvez (en théorie) obtenir une augmentation des performances de 100% pour le même ensemble d'instructions simplement en les réorganisant . Les bons compilateurs génèrent également souvent plus d' instructions d'assemblage pour obtenir un code plus rapide (pensez au déroulement de la boucle, en ligne). Le nombre total d'instructions de l'assembleur en dit très peu sur les performances d'un morceau de code.

nikie
la source
Une autre raison pour de meilleures optimisations est la prise en charge native des nombres complexes.
SK-logic
Certainement correct pour Fortran IV ou plus. Je ne sais pas si les FORTRAN modernes n'ont toujours pas de pointeurs, de mémoire dynamique, etc.
Ingo
2
C'est la même raison pour laquelle nous sommes souvent tombés sur un peu d'assemblage en ligne lors du développement en C et C ++ dans l'industrie des jeux. Les gens peuvent prétendre aussi souvent qu'ils le souhaitent que «les compilateurs peuvent optimiser mieux que les humains écrivant l'assemblage», le fait est que l'alias de pointeur signifie qu'ils ne le peuvent souvent pas . Le code que nous pouvons écrire à la main serait techniquement illégal pour le compilateur à émettre, sachant qu'il ne fait rien sur l'alias de pointeur.
Carson63000
5
Le restrictmot clé C permet à l'auteur d'une fonction de spécifier qu'un pointeur n'a pas d'alias. Est-ce suffisant pour combler la différence, ou y a-t-il plus?
bk.
@bk .: les attaques «restreintes» de C «la moitié du problème»; cela permet de dire qu'un pointeur spécifique n'aliasera rien d'autre pendant sa durée de vie, mais il n'y a aucun moyen de dire à un compilateur qu'un objet dont l'adresse a été passée à une fonction ne sera pas aliasé par quoi que ce soit une fois que cette fonction sera retournée.
supercat
8

Comparaison complètement invalide.

Tout d'abord, comme le souligne @ Péter Török, vous devez d'abord comparer le nombre de lignes dans des programmes équivalents de Fortran et C pour que cela soit même une comparaison valide sur le nombre de lignes produites.

Deuxièmement, moins de lignes de code ne sont pas toujours égales à des programmes plus rapides . Toutes les instructions de la machine ne prennent pas le même nombre de cycles à exécuter , mais vous avez également d'autres problèmes tels que l' accès à la mémoire , la mise en cache , etc.

En plus de cela, les longues exécutions de code peuvent être plus rapides car elles entraînent un nombre inférieur de lignes d'exécution (c'est-à-dire, nombre de lignes ! = Nombre de lignes exécutées ).

Dan McGrath
la source
5

Dan a raison, des programmes plus longs ne signifient pas des programmes plus lents. Cela dépend beaucoup de ce qu'ils font.

Je ne suis pas un expert de Fortran, j'en sais un peu. En les comparant, je pense qu'un C bien écrit ferait beaucoup mieux en termes de performances avec des structures de données et des fonctionnalités plus complexes que Fortran. Quelqu'un (s'il vous plaît) corrigez-moi si je me trompe ici, mais je pense que Fortran est quelque peu à un «niveau inférieur» par rapport à C. Si c'est le cas, je suis sûr que certains problèmes pourraient survenir plus rapidement sur Fortran.

Une autre chose, à première vue, je pensais que vous demandiez si les compilateurs sont plus rapides. En fait, je pense que Fortran compilera généralement plus rapidement pour des quantités similaires de code, mais le programme résultant et son fonctionnement seraient une autre histoire. C'est juste plus simple à analyser.

Garet Claborn
la source
2
Si vous utilisez des structures de données complexes, FORTRAN n'est probablement pas le bon choix. FORTRAN est optimisé pour effectuer des calculs de nombres simples très rapidement.
Zachary K
4

Je pense que cela tient en partie au fait que les compilateurs FORTRAN sont conçus pour effectuer certains types de calculs très rapidement. C'est pourquoi les gens utilisent FORTRAN, pour faire des calculs le plus rapidement possible

Zachary K
la source
4

La déclaration était peut-être vraie dans le passé (vers la fin des années 70) lorsque C était à ses balbutiements, et Fortran était pris en charge par tous les principaux fabricants et était hautement optimisé. Les premiers Fortrans étaient basés sur l'architecture IBM, des trucs aussi simples comme l'arithmétique auraient certainement été une instruction par instruction d'assemblage. Cela est vrai des machines plus anciennes comme Data General et Prime, qui avaient des sauts à 3 voies. Cela ne fonctionne pas sur les jeux d'instructions modernes qui n'ont pas de saut à 3 voies.

Les lignes de code ne correspondent pas aux déclarations de code. Les versions antérieures de Fortran ne permettaient qu'une seule instruction par ligne. Les versions ultérieures de Fortran peuvent accepter plusieurs instructions par ligne. C peut avoir plusieurs instructions par ligne. Sur les compilateurs de production plus rapides comme IVF d'Intel (anciennement CVF, MS Powerstation) et C d'Intel, il n'y a vraiment aucune différence entre les deux. Ces compilateurs sont hautement optimisés.

tasse
la source
4

FORTRAN à l'ancienne exigeait qu'un programmeur qui voulait mettre une partie d'un tableau à la disposition d'une fonction ait besoin de passer une référence à l'ensemble du tableau, ainsi qu'une ou plusieurs valeurs entières spécifiant l'indice de départ et soit l'indice de fin, soit le nombre d'éléments . C permet de simplifier cela en passant un pointeur sur le début de la portion d'intérêt avec le nombre d'éléments. En termes directs, cela accélérerait les choses (en passant deux choses plutôt que trois). Indirectement, cependant, cela peut finir par ralentir les choses en limitant les types d'optimisation qu'un compilateur peut effectuer.

Considérez la fonction:

void diff(float dest[], float src1[], float src2[], int n)
{
  for (int i=0; i<n; i++)
    dest[i] = src1[i] - src2[i];
}

si un compilateur savait que chacun des pointeurs identifierait le début d'un tableau, il pourrait générer du code qui agirait sur les éléments du tableau en parallèle ou dans n'importe quel ordre, car pour tout x! = y, les opérations sur dest [x ] n'affectera pas src1 [y] ni src2 [y]. Par exemple, sur certains systèmes, un compilateur peut bénéficier de la génération de code équivalent à:

void dif(float dest[], float src1[], float src2[], int n)
{
  int i=0;
  float t1a,t1b,t2a,t2b,tsa,tsb;
  if (n > 2)
  {
    n-=4;
    t1a = src1[n+3]; t1b = src2[n+3]; t1b=src2[n+2]; t2b = src2[n+2];
    do
    {
      tsa = t1a-t2a;
      t1a = src1[n+1]; t2a = src2[n+1]; 
      tsb = t2b-t2b;
      dest[n+3] = tsa;
      t1b = src1[n]; t2b = src2[n]; 
      n-=2;
      dest[n+4] = tsb;
    } while(n >= 0);
    ... add some extra code to handle cleanup
  }
  else
    ... add some extra code to handle small values of n
}

Notez que chaque opération qui charge ou calcule une valeur a au moins une opération de plus entre elle et l'opération suivante qui utilise cette valeur. Certains processeurs peuvent chevaucher le traitement de différentes opérations lorsque ces conditions sont remplies, améliorant ainsi les performances. Notez cependant que, comme un compilateur C n'a aucun moyen de savoir que le code ne passera pas de pointeurs vers des régions se chevauchant partiellement d'un tableau commun, un compilateur C ne peut pas effectuer la transformation ci-dessus. Cependant, les compilateurs FORTRAN à code équivalent pouvaient et ont fait une telle transformation.

Alors qu'un programmeur C pourrait tenter d'obtenir des performances comparables en écrivant explicitement du code qui déroulait la boucle et chevauchait les opérations des passes adjacentes, un tel code pourrait facilement dégrader les performances s'il utilisait tant de variables automatiques qu'un compilateur devait les "renverser" sur Mémoire. L'optimiseur d'un compilateur FORTRAN en connaîtrait probablement plus qu'un programmeur sur les formes d'entrelacement qui fourniraient des performances optimales dans un scénario donné, et il est souvent préférable de laisser ces décisions à ces compilateurs. Alors que C99 tentait d'améliorer quelque peu la situation de C en ajoutant un restrictqualificatif, cela ne pouvait être utilisé ici que s'il dest[]s'agissait d'un tableau distinct des deux src1[]et src2[], ou si le programmeur avait ajouté des versions distinctes de la boucle pour gérer les cas où tout destétait disjoint desrc1et src2, où src1[]et destétaient égaux et src2étaient disjoints, où src2[]et dest[]étaient égaux et src1étaient disjoints, et où les trois tableaux étaient égaux. FORTRAN, en revanche, pouvait gérer les quatre cas sans difficulté en utilisant le même code source et le même code machine.

supercat
la source