Théoriquement parlant, est-il possible d'avoir un compilateur Lisp / Scheme capable de produire du code pouvant rivaliser avec le C compilé, disons dans une marge de 15-25%?
Lors de mes tests, j'ai constaté que la gamme actuelle de compilateurs (Bigloo, SBCL, Gambit, Chicken, etc.) est 20 à 50 fois plus lente que le code C équivalent .
La seule valeur aberrante est le compilateur Staline . Pour les programmes simples, il produit des binaires équivalents à C. Cependant, ce que je trouve suspect, c'est qu'aucun des autres projets (Bigloo, Chicken, Clozure, etc.) n'a tenté d'implémenter les astuces que Staline utilise ("optimisation du programme entier", etc).
Je suis un grand fan de LISP depuis le milieu des années 90 et j'aimerais l'intégrer pour que mon équipe puisse lancer des projets en deux fois moins de temps en utilisant normalement C / C ++ / .NET / etc, mais ... les performances les problèmes sont un énorme barrage routier.
Je me demande si le manque de compilateurs LISP de qualité est dû au fait qu'aucun temps et argent sérieux n'ont été investis dans le sujet OU si ce n'est tout simplement pas une tâche réalisable étant donné l'état actuel de la technologie du compilateur ??
la source
(declare (optimize ...))
,(declare (<type> <var))
et(the <type> <expr>)
dans vos fonctions? Sinon, ce n'est pas une comparaison juste :)Réponses:
Comme indiqué dans les commentaires, il n'est pas très exact de dire "la récolte actuelle de compilateurs (Bigloo, SBCL, Gambit, Chicken, etc.) est 20 à 50 fois plus lente que le code C équivalent", sans qualifier la façon dont vous avez testé et ce que vous avez testé.
Pour mon usage, je trouve que pour beaucoup de choses Gambit et Chicken Scheme sont assez proches du code équivalent 'C' en vitesse, avec la version actuelle de Gambit (4.7.3) généralement plus rapide que Chicken (4.9.0.1) mais sur pré -optimiserle code de sortie 'C' (en faisant des hypothèses sur le nombre de registres disponibles - suppose x686 - et en forçant l'utilisation de la mémoire de la pile pour toute exigence de mémoire supplémentaire, quelles décisions devraient être laissées au compilateur 'C' comme le fait Chicken, ce qui élimine souvent l'exigence de registres supplémentaires et de combiner les étapes de traitement) afin d'empêcher le compilateur «C» de faire ses propres optimisations, ce qui fait que les petites boucles très serrées sont jusqu'à environ deux fois plus lentes que ces mêmes petites boucles serrées en «C» (ou Chicken ); Chicken définit simplement autant de variables locales pour une fonction donnée qu'il le juge utile (principalement utilisé de manière immuable) et dépend ensuite du compilateur pour optimiser la plupart de celles-ci. Poulet ne fait pas
EDIT_ADD: J'ai fait des recherches supplémentaires sur les versions du programme Chicken et Gambit-C et j'ai trouvé ce qui suit:
Chicken est plus rapide que Gambit pour les petites boucles serrées pour la raison ci-dessus (dépend plus du compilateur `` C '' pour les optimisations sans faire autant de lui-même et profite donc mieux des registres supplémentaires x86-64), mais aussi parce qu'il n'inclut pas un contrôle de maintenance de pile "POLL" à l'intérieur des boucles, alors que Gambit inclut le contrôle "POLL" à l'intérieur de la boucle. Même lorsque cela n'est pas déclenché (le cas habituel), il faudra plusieurs cycles d'horloge CPU pour déterminer que rien n'est requis (environ 6 cycles). Un futur compilateur plus intelligent pourrait voir qu'il n'est pas nécessaire de vérifier la pile à l'intérieur d'une boucle serrée et de ne pas effectuer d'opérations de création de pile, le faire juste avant ou après la boucle et gagner du temps.
Les macros `` C '' de Gambit font trop de travail, comme indiqué, pour définir précisément la façon dont les opérations doivent être effectuées, notamment les opérations de taille de pile fixe, et celles-ci sont probablement plus difficiles à optimiser pour le compilateur `` C ''; une utilisation plus efficace des registres pourrait réduire le temps de boucle serrée de peut-être 4 cycles, ce qui, combiné avec ce qui précède, le placerait dans le stade de baseball du poulet.
Aucun des deux ne produit des optimisations "lecture / modification / écriture" pour les opérations dites vectorielles qui sont modifiées sur place et ne produisent pas de code pour que le compilateur le fasse. Un backend plus intelligent tel que LLVM lorsqu'il est utilisé avec Haskell fait ce genre de chose. Cela réduirait l'utilisation du registre d'un temps d'exécution en n'utilisant qu'une seule instruction plutôt qu'une lecture séparée, le calcul de la modification et une écriture au même emplacement; cela deviendrait juste un calcul de la modification (disons un bit ou), puis une instruction unique de modification de lecture (| =) d'écriture. Cela pourrait rendre la vitesse plus rapide d'un cycle ou deux
Les deux sont typés dynamiquement et traitent les bits de «balise» de données dans le cadre de leurs données. Ni l'un ni l'autre ne sont assez intelligents pour que les boucles serrées réduisent les balises, effectuent la boucle "sans balise", puis rajoutent les balises pour tous les résultats de la boucle, et ne produisent pas de code où le compilateur "C" peut voir pour le faire bien que il combine ces opérations dans certains cas. L'optimisation ici pourrait réduire les temps de boucle de quelques cycles CPU environ, selon la boucle.
Les boucles «C» très serrées peuvent prendre environ 3,5 cycles d'horloge du processeur par boucle sur un processeur rapide qui n'est pas limité par la vitesse d'accès au cache mémoire (comme AMD Bulldozer, qui est environ deux fois plus lent); la même boucle dans Chicken prend actuellement environ 6 cycles et Gambit prend environ 16,9 cycles. Avec toutes les optimisations décrites ci-dessus, il n'y a aucune raison que ces implémentations de Scheme ne puissent pas faire cela, cependant un travail est nécessaire:
Dans le cas de Gambit, le travail le plus difficile pourrait être d'améliorer l'analyse de flux pour reconnaître quand aucun test "POLL" ne doit être inséré (c.-à-d. Cela pourrait-il être déclenché par interruption, bien que le compilateur permette de désactiver les interruptions pour certaines utilisations? ); l'amélioration la plus facile serait de simplement implémenter une meilleure utilisation des registres (c'est-à-dire reconnaître mieux les registres x86-64 plutôt que l'architecture x686 par défaut). Pour les deux, une meilleure analyse de flux pour reconnaître qu'ils peuvent «décocher» les données, en particulier les données «fixnum», «flonum» et vectorielles, de sorte que ces opérations ne sont pas nécessaires à l'intérieur de boucles serrées et combinant des instructions de lecture / modification / écriture. Ces deux fins pourraient être accomplies en utilisant un meilleur backend tel que LLVM (pas une quantité de travail insignifiante, mais les deux sont déjà partiellement là).
Conclusion: Chicken est actuellement environ 50% plus lent que 'C' sur les processeurs les plus rapides (pas mon Bulldozer, où il est à peu près à la même vitesse en raison de la limitation du cache du code 'C') et Gambit est environ 400% plus lent (seulement environ 125% plus lent sur mon Bulldozer lent). Cependant, les améliorations futures des compilateurs pourraient réduire cela afin que le code ne soit pas plus lent que «C» ou dans la marge spécifiée par l'OP.
Un langage plus complexe tel que Haskell, lors de l'utilisation du backend LLVM, en accordant une attention particulière à l'utilisation de la rigueur (pas un problème avec le schéma qui est toujours désireux par défaut), et en utilisant des structures de données appropriées (tableaux non encadrés ST plutôt que des listes de boucles serrées; quelque peu applicable au schéma utilisant des vecteurs), fonctionne à peu près à la même vitesse que «C» si le backend LLVM est utilisé avec des optimisations complètes. S'il peut le faire, Scheme peut le faire avec les améliorations du compilateur ci-dessus.
ENCORE, il n'y a aucun moyen que l'un ou l'autre soit 20 à 50 fois plus lent lorsqu'il est utilisé avec des indicateurs d'optimisation appropriés. END_EDIT_ADD
Bien sûr, tous les benchmarks sont invalidés si l'on n'utilise pas les paramètres d'optimisation appropriés pour chacun comme on le ferait dans un environnement de production ...
Je pense que le compilateur commercial Chez Scheme serait sur le point de produire une sortie haute performance comme Gambit et Chicken, car étant commercial, il a sûrement "un temps et de l'argent sérieux investis".
La seule façon dont je peux faire fonctionner Gambit ou Chicken aussi lentement que "20 à 50 fois plus lentement que 'C'" est de ne pas utiliser de paramètres d'optimisation, auquel cas ils ne fonctionnent souvent pas beaucoup plus vite que tel qu'interprété dans leurs REPL. - 10 fois plus lent qu'en utilisant correctement ces paramètres.
Est-il possible que l'OP n'ait pas testé correctement en utilisant ces paramètres?
Si l'OP tient à clarifier ses procédures de test, je serai ravi de modifier cette réponse pour montrer qu'au moins Gambit et Chicken ne doivent pas être aussi lents.
la source