Voici quelques-unes de mes découvertes à jour, bien qu'étroites, avec GCC 4.7.2 et Clang 3.2 pour C ++.
MISE À JOUR: GCC 4.8.1 v clang 3.3 comparaison ci-dessous.
MISE À JOUR: La comparaison GCC 4.8.2 v clang 3.4 y est annexée.
Je maintiens un outil OSS conçu pour Linux avec GCC et Clang, et avec le compilateur de Microsoft pour Windows. L'outil, coan, est un préprocesseur et un analyseur de fichiers source C / C ++ et de lignes de codage de ceux-ci: son profil de calcul se spécialise dans l'analyse et la gestion de fichiers à descente récursive. La branche développement (à laquelle ces résultats se rapportent) comprend actuellement environ 11K LOC dans environ 90 fichiers. Il est codé, maintenant, en C ++ qui est riche en polymorphisme et en modèles et est encore embourbé dans de nombreux correctifs par son passé pas si lointain dans le C. piraté. La sémantique de Move n'est pas expressément exploitée. Il est monofil. Je n'ai consacré aucun effort sérieux à l'optimiser, alors que «l'architecture» reste si largement ToDo.
J'ai utilisé Clang avant 3.2 uniquement comme compilateur expérimental parce que, malgré sa vitesse de compilation et ses diagnostics supérieurs, sa prise en charge standard C ++ 11 était en retard sur la version GCC contemporaine dans les égards exercés par coan. Avec 3.2, cet écart a été comblé.
Mon harnais de test Linux pour les processus de développement actuels de coan représente environ 70 000 fichiers sources dans un mélange de cas de test d'analyseur à un fichier, de tests de résistance consommant des milliers de fichiers et de tests de scénario consommant <1 000 fichiers. En plus de rapporter les résultats du test, le faisceau accumule et affiche les totaux des fichiers consommés et le temps d'exécution consommé en coan (il passe simplement chaque ligne de commande coan à la time
commande Linux et capture et additionne les nombres rapportés). Les délais sont flattés par le fait que n'importe quel nombre de tests qui prennent 0 temps mesurable totalisera tous jusqu'à 0, mais la contribution de ces tests est négligeable. Les statistiques de chronométrage sont affichées à la fin de make check
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 faisceau de test entre GCC 4.7.2 et Clang 3.2, toutes choses étant égales par ailleurs, sauf les compilateurs. Depuis Clang 3.2, je n'ai plus besoin de différenciation de préprocesseur entre les voies de code que GCC compilera et les alternatives de Clang. J'ai construit dans la même bibliothèque C ++ (GCC) dans chaque cas et exécuté toutes les comparaisons consécutivement dans la même session de terminal.
Le niveau d'optimisation par défaut pour ma version de version est -O2. J'ai également testé avec succès des versions à -O3. J'ai testé chaque configuration 3 fois consécutivement et fait la moyenne des 3 résultats, avec les résultats suivants. Le nombre dans une cellule de données est le nombre moyen de microsecondes consommées par l'exécutable COAN pour traiter chacun des fichiers d'entrée ~ 70K (lecture, analyse et écriture 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 a très probablement des caractéristiques qui jouent injustement avec les forces ou les faiblesses d'un compilateur. Une analyse comparative rigoureuse utilise diverses applications. Dans cet esprit, les caractéristiques remarquables de ces données sont les suivantes:
- -O3 optimisation a été légèrement préjudiciable à GCC
- -O3 optimisation a été très bénéfique pour Clang
- À l'optimisation -O2, GCC était plus rapide que Clang par juste un moustache
- À l'optimisation -O3, Clang était beaucoup plus rapide que GCC.
Une autre comparaison intéressante des deux compilateurs est apparue par accident peu de temps après ces résultats. Coan emploie généreusement des pointeurs intelligents et l'un d'entre eux est fortement exercé dans la gestion des fichiers. Ce type de pointeur intelligent particulier avait été tapé dans les versions précédentes à des fins de différenciation du compilateur, pour être un std::unique_ptr<X>
si le compilateur configuré avait un support suffisamment mature pour son utilisation comme ça, et sinon un std::shared_ptr<X>
. Le biais à std::unique_ptr
était stupide, car ces pointeurs étaient en fait transférés, mais std::unique_ptr
ressemblaient à l'option de montage pour le remplacement
std::auto_ptr
à un moment où les variantes C ++ 11 étaient nouvelles pour moi.
Au cours de builds expérimentaux pour évaluer le besoin continu de Clang 3.2 pour cela et une 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 par défaut -O2, était le plus rapide que je avait vu, atteignant parfois 184 ms. par fichier d'entrée. Avec cette seule modification du code source, les résultats correspondants étaient les suivants;
| -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 à noter ici sont:
- Aucun des deux compilateurs ne bénéficie désormais de l'optimisation -O3.
- Clang bat GCC tout aussi important à chaque niveau d'optimisation.
- Les performances de GCC ne sont que marginalement affectées par le changement de type de pointeur intelligent.
- Les performances de -ang O2 de Clang sont affectées de manière importante par le changement de type de pointeur intelligent.
Avant et après le changement de type de pointeur intelligent, Clang est en mesure de créer un exécutable COAN beaucoup plus rapide à l'optimisation -O3, et il peut créer un exécutable tout aussi rapide à -O2 et -O3 lorsque ce type de pointeur est le meilleur - std::shared_ptr<X>
- Pour le boulot.
Une question évidente que je ne suis pas compétent pour commenter est pourquoi
Clang devrait être en mesure de trouver une accélération de 25% -O2 dans mon application lorsqu'un type de pointeur intelligent très utilisé est passé d'unique à partagé, alors que GCC est indifférent au même changement. Je ne sais pas non plus si je devrais encourager ou huer la découverte que l'optimisation -O2 de Clang abrite une sensibilité aussi énorme à 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 désormais beaucoup plus de temps en moyenne qu'auparavant pour traiter 1 fichier ne reflète pas les performances des derniers compilateurs. Cela est dû au fait que la branche de développement ultérieure de l'application de test a entre-temps pris beaucoup de sophistication d'analyse et la paie rapidement. Seuls les ratios sont significatifs.
Les points à noter ne sont pas d'une nouveauté saisissante:
- GCC est indifférent à l'optimisation -O3
- clang bénéficie très marginalement de l'optimisation -O3
- clang bat GCC d'une marge tout aussi importante à chaque niveau d'optimisation.
En comparant ces résultats avec ceux de GCC 4.7.2 et clang 3.2, il ressort que GCC a récupéré environ un quart de l'avance de clang à 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 le réutiliser.)
MISE À JOUR: GCC 4.8.2 v clang 3.4
J'ai terminé la mise à jour de GCC 4.8.1 v Clang 3.3 en disant que je m'en tiendrais au même instantané de coan pour d'autres mises à jour. Mais j'ai décidé à la place de tester sur cet instantané (rév. 301) et sur le dernier instantané de développement que j'ai qui réussit sa suite de tests (rév. 619). Cela donne aux résultats un peu de longitude, et j'avais un autre motif:
Mon message d'origine indiquait que je n'avais consacré aucun effort à optimiser le rouage pour la vitesse. C'était encore le cas au rév. 301. Cependant, après avoir intégré l'appareil de chronométrage dans le harnais de test coan, chaque fois que je courais la suite de tests, l'impact sur les performances des derniers changements me regardait en face. J'ai vu qu'elle était souvent étonnamment grande et que la tendance était plus fortement négative que je ne le pensais méritée par des gains de fonctionnalité.
Par rev. 308 le temps de traitement moyen par fichier d'entrée dans la suite de tests avait bien plus que doublé depuis la première publication ici. À ce stade, j'ai fait volte-face sur ma politique de 10 ans de ne pas se soucier de la performance. Dans la vague intensive de révisions, jusqu'à 619, les performances ont toujours été prises en compte et un grand nombre d'entre elles sont allées uniquement à la réécriture de supports de charge clés sur des lignes fondamentalement plus rapides (mais sans utiliser de fonctionnalités de compilateur non standard pour le faire). Il serait intéressant de voir la réaction de chaque compilateur à ce demi-tour,
Voici la matrice de synchronisation désormais familière pour les deux dernières compilations de la version 301 des compilateurs:
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 n'est que légèrement modifiée par rapport à GCC-4.8.1 et Clang-3.3. La performance de GCC est un peu meilleure. Clang est un peu pire. Le bruit pourrait bien expliquer cela. Clang sort toujours en tête -O2
et des -O3
marges 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 côte à côte les figures 301 et 619, plusieurs points se manifestent.
Je visais à écrire du code plus rapidement, et les deux compilateurs justifient avec force mes efforts. Mais:
Le CCG rembourse ces efforts beaucoup plus généreusement que Clang. À l' -O2
optimisation, la version 619 de Clang est 46% plus rapide que sa version 301: à -O3
Clang, l'amélioration est de 31%. Bien, mais à chaque niveau d'optimisation, la version 619 de GCC est plus de deux fois plus rapide que sa 301.
GCC fait plus qu'annuler l'ancienne supériorité de Clang. Et à chaque niveau d'optimisation, GCC bat désormais Clang de 17%.
La capacité de Clang dans la version 301 à obtenir plus de levier que GCC grâce à l' -O3
optimisation a disparu dans la version 619. Aucun des compilateurs ne gagne de manière significative -O3
.
J'ai été suffisamment surpris par ce renversement de fortune pour que je soupçonne que j'ai pu accidentellement créer une version lente de clang 3.4 lui-même (puisque je l'ai construit à partir de la source). J'ai donc relancé le test 619 avec le stock de ma distribution Clang 3.3. 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 en principe, mais je prends la leçon que "Quel compilateur produit les meilleurs binaires?" est une question qui, 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 simplement chronométrer les binaires.
Votre meilleur binaire est-il le binaire le plus rapide, ou est-ce celui qui compense le mieux le code conçu à moindre coût? Ou compense-t-il mieux le
code élaboré de manière coûteuse qui donne la priorité à la maintenabilité et à la réutilisation par rapport à la vitesse? Cela dépend de la nature et des poids relatifs de vos motifs de production du binaire et des contraintes sous lesquelles vous le faites.
Et dans tous les cas, si vous vous souciez profondément de créer "les meilleurs" binaires, vous feriez mieux de continuer à vérifier comment les itérations successives des compilateurs donnent suite à votre idée du "meilleur" par rapport aux itérations successives de votre code.