optimization - Clang vs GCC - qui produit de meilleurs binaires?




4 Answers

Voici quelques résultats mis à jour, quoique étroits, des miens avec GCC 4.7.2 et Clang 3.2 pour C ++.

MISE À JOUR: GCC 4.8.1 v clang 3.3 comparaison ci-dessous.

UPDATE: GCC 4.8.2 v clang 3.4 comparaison est ajouté à cela.

Je maintiens un outil OSS qui est construit pour Linux avec GCC et Clang, et avec le compilateur Microsoft pour Windows. L'outil, coan, est un préprocesseur et un analyseur de fichiers sources C / C ++ et de lignes de codage telles que: ses majors de profil de calcul sur l'analyse par récursivité-descente et la gestion de fichiers. La branche développement (à laquelle se rapportent ces résultats) comprend actuellement environ 11K LOC dans environ 90 fichiers. Il est codé, maintenant, en C ++ qui est riche en polymorphismes et en templates mais qui est encore embourbé dans beaucoup de patches par son passé pas si lointain en hacked-together. La sémantique de C. Move n'est pas explicitement exploitée. C'est un seul thread. Je n'ai consacré aucun effort sérieux pour l'optimiser, alors que "l'architecture" reste si largement utile.

J'ai employé Clang avant 3.2 seulement comme compilateur expérimental parce que, en dépit de sa vitesse de compilation et diagnostics supérieurs, son support standard de C ++ 11 a traîné la version contemporaine de GCC dans les respects exercés par coan. Avec 3.2, cet écart a été fermé.

Mon harnais de test Linux pour les processus actuels de développement Coan traite environ 70K fichiers source dans un mélange de cas de test d'analyseur à un fichier, des tests de stress consommant 1000s de fichiers et des tests de scénarios consommant des fichiers <1K. En plus de rapporter les résultats du test, le harnais accumule et affiche les totaux des fichiers consommés et le temps d'exécution consommé dans coan (il passe juste chaque ligne de commande coan à la commande Linux time et capture et additionne les nombres rapportés). Les timings sont flattés par le fait qu'un nombre quelconque de tests qui prennent 0 temps mesurable totaliseront 0, mais la contribution de ces tests est négligeable. Les statistiques de timing sont affichées à la fin de make check comme ceci:

coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.

J'ai comparé les performances du harnais de test entre GCC 4.7.2 et Clang 3.2, toutes choses étant égales sauf les compilateurs. À partir de Clang 3.2, je n'ai plus besoin d'une différenciation de préprocesseur entre les tracés de code que compilera GCC et les alternatives Clang. J'ai construit dans la même bibliothèque C ++ (GCC) dans chaque cas et a couru toutes les comparaisons consécutivement dans la même session de terminal.

Le niveau d'optimisation par défaut pour ma version finale est -O2. J'ai également testé avec succès des builds à -O3. J'ai testé chaque configuration trois fois consécutivement et fait la moyenne des trois résultats, avec les résultats suivants. Le nombre dans une cellule de données est le nombre moyen de microsecondes consommées par le programme exécutable pour traiter chacun des ~ 70K fichiers d'entrée (lecture, analyse et sortie de sortie et diagnostics).

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|

Toute application particulière est très susceptible d'avoir des caractéristiques qui jouent injustement aux forces ou aux faiblesses d'un compilateur. L'analyse comparative rigoureuse utilise diverses applications. Dans cet esprit, les caractéristiques remarquables de ces données sont:

  1. -O3 optimisation était marginalement préjudiciable à GCC
  2. -O3 optimisation était important bénéfique pour Clang
  3. À l'optimisation de -O2, GCC était plus rapide que Clang
  4. À l'optimisation -O3, Clang était plus important que GCC.

Une autre comparaison intéressante des deux compilateurs est apparue par hasard peu de temps après ces découvertes. Coan emploie généreusement des pointeurs intelligents et l'un de ceux-ci est fortement utilisé dans le traitement des fichiers. Ce type particulier de pointeur intelligent a été typedef'd dans les versions antérieures pour la différenciation du compilateur, pour être un std::unique_ptr<X> si le compilateur configuré avait un support suffisamment mature pour son utilisation comme cela, et sinon un std::shared_ptr<X> . Le biais de std::unique_ptr était stupide, puisque ces pointeurs étaient en fait transférés, mais std::unique_ptr ressemblait à l'option installateur pour remplacer std::auto_ptr à un moment où les variantes C ++ 11 étaient nouvelles pour moi.

Au cours des builds expérimentaux pour évaluer le besoin continu de Clang 3.2 pour cette différenciation similaire, j'ai construit par inadvertance std::shared_ptr<X> quand j'avais l'intention de construire std::unique_ptr<X> , et j'ai été surpris de constater que l'exécutable résultant, avec l'optimisation -O2 par défaut, était le plus rapide que j'avais vu, atteignant parfois 184 ms. par fichier d'entrée. Avec celui-ci changer au code source, les résultats correspondants étaient ceux-ci;

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |

Les points de note ici sont:

  1. Aucun compilateur ne profite maintenant du tout de l'optimisation -O3.
  2. Clang bat GCC tout aussi important à chaque niveau d'optimisation.
  3. Les performances de GCC ne sont que légèrement affectées par la modification de type smart-pointer.
  4. La performance de Clang -O2 est affectée de manière importante par le changement de type de pointeur intelligent.

Avant et après le changement de type smart-pointer, Clang est capable de construire un exécutable coan sensiblement plus rapide à l'optimisation -O3, et il peut construire un exécutable tout aussi rapide à -O2 et -O3 quand ce type de pointeur est le meilleur - std::shared_ptr<X> - pour le travail.

Une question évidente que je ne suis pas compétent pour commenter est pourquoi Clang devrait être capable de trouver une accélération de 25% -O2 dans mon application quand un type de pointeur intelligent fortement utilisé est changé d'unique en partagé, alors que GCC est indifférent au même changement. Je ne sais pas non plus si je devrais applaudir ou boo la découverte que l'optimisation -O2 de Clang héberge une telle sensibilité à la sagesse de mes choix de pointeurs intelligents.

MISE À JOUR: GCC 4.8.1 v clang 3.3

Les résultats correspondants sont maintenant:

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |

Le fait que les quatre exécutables prennent maintenant beaucoup plus de temps que précédemment pour traiter un fichier ne reflète pas les performances des derniers compilateurs. Cela est dû au fait que la dernière branche de développement de l'application de test a pris beaucoup de sophistication d'analyse entre-temps et paye en vitesse. Seuls les ratios sont significatifs.

Les points importants ne sont pas nouveaux:

  • GCC est indifférent à l'optimisation -O3
  • avantages clang très marginalement de -O3 optimisation
  • clang bat GCC par une marge aussi importante à chaque niveau d'optimisation.

En comparant ces résultats avec ceux de GCC 4.7.2 et de 3.2, il ressort que GCC a récupéré environ un quart de son avance à chaque niveau d'optimisation. Mais puisque l'application de test a été fortement développée entre-temps, on ne peut pas l'attribuer en toute confiance à un rattrapage dans la génération de code de GCC. (Cette fois, j'ai noté l'instantané de l'application à partir duquel les timings ont été obtenus et je peux l'utiliser à nouveau.)

MISE À JOUR: GCC 4.8.2 v clang 3.4

J'ai terminé la mise à jour pour GCC 4.8.1 v Clang 3.3 en disant que je resterais sur le même snaphot Coan pour d'autres mises à jour. Mais j'ai décidé plutôt de tester sur cet instantané (rev.301) et sur le dernier instantané de développement que j'ai qui passe sa suite de tests (rev.619). Cela donne un peu de longitude aux résultats, et j'avais un autre motif:

Mon message original a noté que je n'avais consacré aucun effort pour optimiser le coan pour la vitesse. C'était toujours le cas à partir de rev. 301. Cependant, après avoir intégré l'appareil de chronométrage dans le harnais de test de coan, chaque fois que j'ai couru la suite de tests, l'impact sur les performances des derniers changements m'a regardé droit dans les yeux. J'ai vu que c'était souvent étonnamment grand et que la tendance était plus fortement négative que ce que je pensais mériter par des gains de fonctionnalité.

Par rev. 308 le temps de traitement moyen par fichier d'entrée dans la suite de tests avait plus que doublé depuis la première publication ici. À ce moment-là, j'ai fait demi-tour sur ma politique de 10 ans de ne pas déranger sur la performance. Dans la vague intensive de révisions jusqu'à 619, la performance était toujours un facteur à prendre en considération et un grand nombre d'entre eux passaient uniquement à réécrire les porteurs de charge clés sur des lignes fondamentalement plus rapides (sans utiliser de fonctionnalités de compilation non standard). Il serait intéressant de voir la réaction de chaque compilateur à ce demi-tour,

Voici la matrice des timings maintenant familiers pour les deux derniers compilateurs de rev.301:

coan - rev.301 résultats

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|

L'histoire ici est seulement légèrement modifiée de GCC-4.8.1 et Clang-3.3. L'émission de GCC est un peu mieux. Clang est un peu pire. Le bruit pourrait bien expliquer cela. Clang arrive toujours en tête avec des marges de -O2 et -O3 qui n'auraient pas d'importance dans la plupart des applications mais qui seraient importantes pour quelques-unes.

Et voici la matrice pour rev. 619.

coan - rev.619 résultats

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|

Prenant les chiffres 301 et 619 côte à côte, plusieurs points parlent.

  • Je visais à écrire du code plus rapidement, et les deux compilateurs justifient énergiquement mes efforts. Mais:

  • GCC rembourse ces efforts beaucoup plus généreusement que Clang. Optimisation à -O2 La version 619 de Clang est 46% plus rapide que sa version 301: à -O3 l'amélioration de Clang est de 31%. Bon, mais à chaque niveau d'optimisation, la version 619 de GCC est plus de deux fois plus rapide que sa version 301.

  • GCC renverse plus que l'ancienne supériorité de Clang. Et à chaque niveau d'optimisation, GCC bat maintenant Clang de 17%.

  • La capacité de Clang dans la version 301 à obtenir plus d'effet de levier que GCC à partir de l'optimisation -O3 disparu dans la version 619. Ni compilateur gagne significativement de -O3 .

J'ai été suffisamment surpris par ce renversement de fortunes que je soupçonnais d'avoir accidentellement fait une construction léthargique de clang 3.4 (depuis que je l'ai construit à partir de la source). J'ai donc refait le test 619 avec le stock Clang 3.3 de ma distribution. Les résultats étaient pratiquement les mêmes que pour 3.4.

Donc, en ce qui concerne la réaction au demi-tour: Sur les chiffres ici, Clang a fait beaucoup mieux que GCC à la vitesse d'essorage de mon code C ++ quand je ne lui donnais aucune aide. Quand j'ai décidé d'aider, GCC a fait un bien meilleur travail que Clang.

Je n'élève pas cette observation dans un principe, mais je prends la leçon suivante: "Quel compilateur produit les meilleurs binaires?" est une question que, même si vous spécifiez la suite de tests à laquelle la réponse doit être relative, n'est toujours pas une question claire de juste chronométrer les binaires.

Est-ce que votre meilleur binaire est le binaire le plus rapide, ou est-ce celui qui compense le mieux pour du code à petit prix? Ou mieux compense le code élaboré de façon coûteuse qui donne la priorité à la maintenabilité et à la réutilisation par rapport à la vitesse? Cela dépend de la nature et du poids relatif de vos motivations pour produire le binaire, et des contraintes sous lesquelles vous le faites.

Et dans tous les cas, si vous vous souciez de construire les meilleurs binaires, alors vous feriez mieux de vérifier comment les itérations successives des compilateurs livrent votre idée du "meilleur" sur les itérations successives de votre code.

J'utilise actuellement GCC, mais j'ai récemment découvert Clang et je réfléchis à la commutation. Il y a cependant un facteur décisif: la qualité (vitesse, encombrement mémoire, fiabilité) des binaires qu'elle produit - si gcc -O3 peut produire un binaire qui s'exécute 1% plus vite ou prend 1% de mémoire en moins, c'est un deal breaker.

Clang possède de meilleures vitesses de compilation et une plus faible empreinte mémoire à la compilation que GCC, mais je suis vraiment intéressé par les benchmarks / comparaisons des logiciels compilés qui en résultent - pourriez-vous m'en parler ou décrire vos expériences?




Le fait que Clang compile le code plus rapidement peut ne pas être aussi important que la vitesse du binaire résultant. Cependant, voici une série de repères .




La seule façon de déterminer cela est de l'essayer. FWIW J'ai vu de très bonnes améliorations en utilisant le LLVM gcc 4.2 d'Apple comparé au gcc 4.2 normal (pour le code x86-64 avec beaucoup de SSE), mais YMMV pour différentes bases de code. En supposant que vous travaillez avec x86 / x86-64 et que vous vous souciez vraiment des derniers pourcentages, vous devriez aussi essayer l'ICC d'Intel, car cela peut souvent gcc - vous pouvez obtenir une licence d'évaluation de 30 jours sur intel.com et essayez-le.




En gros, la réponse est: ça dépend. Il y a beaucoup de points de repère se concentrant sur différents types d'application.

Mon benchmark sur mon application est: gcc> icc> clang.

Il y a des E / S rares, mais de nombreuses opérations de flottement et de structure de données du processeur.

compiler flags est -Wall -g -DNDEBUG -O3.

https://github.com/zhangyafeikimi/ml-pack/blob/master/gbdt/profile/benchmark




Related

optimization gcc compiler-construction clang benchmarking