Un langage dynamique comme Ruby / Python peut-il atteindre des performances comparables à celles du C / C ++?


64

Je me demande s’il est possible de construire des compilateurs pour des langages dynamiques comme Ruby afin d’avoir des performances similaires et comparables à celles du C / C ++? D'après ce que je comprends des compilateurs, prenons Ruby par exemple, compiler du code Ruby ne peut jamais être efficace, car Ruby gère la réflexion, des fonctionnalités telles que la conversion automatique de type d'un entier à un grand, et l'absence de typage statique rendent la construction d'un compilateur efficace. pour Ruby extrêmement difficile.

Est-il possible de construire un compilateur capable de compiler Ruby ou tout autre langage dynamique en un binaire fonctionnant de manière très proche de C / C ++? Existe-t-il une raison fondamentale pour laquelle les compilateurs JIT, tels que PyPy / Rubinius, finiront par ne jamais être plus performants que C / C ++?

Remarque: je comprends que la «performance» puisse être vague, donc pour clarifier cela, je voulais dire, si vous pouvez utiliser X en C / C ++ avec la performance Y, pouvez-vous utiliser X en Ruby / Python avec une performance proche de Y? Où X représente tout, des pilotes de périphérique et du code de système d'exploitation aux applications Web.


1
Pouvez-vous reformuler la question afin qu'elle encourage les réponses étayées par des preuves appropriées par rapport aux autres?
Raphaël

@ Raphaël Je suis allé de l'avant et édité. Je pense que mon édition ne change pas fondamentalement le sens de la question, mais la rend moins attrayante pour des opinions.
Gilles 'SO- arrête d'être méchant'

1
En particulier, je pense que vous devez fixer une (ou plusieurs) mesures de performance concrètes. Runtime? Utilisation de l'espace? La consommation d'énergie? Temps développeur? Retour sur investissement? Notez également cette question sur notre méta qui concerne cette question (ou plutôt ses réponses).
Raphaël

Cette question est un initiateur typique des guerres de religion. Et comme on peut le voir d'après les réponses, nous en avons un, même s'il est très civilisé.
Andrej Bauer

Il existe des langages dynamiques permettant des annotations de type facultatives (par exemple: Clojure). D'après ce que je sais, les performances associées aux fonctions annotées sont équivalentes à celles dans lesquelles un langage serait typé de manière statique.
Pedro Morte Rolo

Réponses:


68

À tous ceux qui ont dit «oui», je vais proposer que la réponse soit «non», à dessein . Ces langages ne seront jamais capables de rivaliser avec les performances des langages compilés statiquement.

Kos a déclaré (très valable) que les langages dynamiques ont plus d'informations sur le système à l'exécution, ce qui peut être utilisé pour optimiser le code.

Cependant, il y a un autre côté de la médaille: ces informations supplémentaires doivent être conservées. Sur les architectures modernes, ceci est un tueur de performance.

William Edwards offre un bon aperçu de l'argumentation .

En particulier, les optimisations mentionnées par Kos ne peuvent être appliquées au-delà d'une portée très limitée, sauf si vous limitez de manière drastique le pouvoir d'expression de vos langues, comme l'a mentionné Devin. C’est bien sûr un compromis viable, mais aux fins de la discussion, vous vous retrouvez avec un langage statique et non dynamique. Ces langues diffèrent fondamentalement de Python ou de Ruby comme le comprendraient la plupart des gens.

William cite quelques diapositives IBM intéressantes :

  • Chaque variable peut être typée dynamiquement: Vérification du type de besoin
  • Chaque instruction peut potentiellement générer des exceptions en raison d'une incompatibilité de type, etc.: Besoin de vérification des exceptions
  • Chaque champ et symbole peuvent être ajoutés, supprimés et modifiés au moment de l'exécution: besoin de contrôles d'accès
  • Le type de chaque objet et sa hiérarchie de classes peuvent être modifiés à l'exécution: besoin de contrôles de la hiérarchie de classes

Certaines de ces vérifications peuvent être éliminées après analyse (NB: cette analyse prend également du temps - au moment de l'exécution).

En outre, Kos affirme que les langages dynamiques pourraient même surpasser les performances en C ++. JIT peut en effet analyser le comportement du programme et appliquer des optimisations appropriées.

Mais les compilateurs C ++ peuvent faire la même chose! Les compilateurs modernes offrent ce que l’on appelle l’optimisation guidée par le profil qui, s’ils reçoivent les données appropriées, permet de modéliser le comportement d’exécution du programme et d’appliquer les mêmes optimisations qu’un JIT.

Bien sûr, tout dépend de l’existence de données d’entraînement réalistes et, en outre, le programme ne peut pas adapter ses caractéristiques d’exécution si le modèle d’utilisation change en cours d’exécution. Les JIT peuvent théoriquement gérer cela. Cela m'intéresserait de voir comment cela se passera dans la pratique, car pour basculer les optimisations, le JIT devrait continuellement collecter des données d'utilisation, ce qui ralentit encore une fois l'exécution.

En résumé, je ne suis pas convaincu que les optimisations de points chauds d'exécution compensent les frais généraux liés au suivi des informations d'exécution à long terme , par rapport à l'analyse et à l'optimisation statiques.


2
@ Raphaël C'est une "lacune" du compilateur alors. En particulier, une javacoptimisation guidée par le profil a- t-elle déjà eu lieu ? Pas autant que je sache. En général, il n’a aucun sens de faire en sorte que le compilateur d’un langage JITted soit bien optimisé, car le JIT peut le gérer (et à tout le moins, plus de langues tireront profit de cet effort). Donc (ce qui est compréhensible), l' javacoptimiseur n'a jamais fait l'objet de beaucoup d'efforts , pour autant que je sache (pour les langages .NET, c'est absolument vrai).
Konrad Rudolph

1
@ Raphaël Le mot clé est: peut-être. Cela ne le montre pas de toute façon. C'est tout ce que je voulais dire. J'ai donné les raisons (mais aucune preuve) de mon hypothèse dans les paragraphes précédents.
Konrad Rudolph

1
@ Ben, je ne nie pas que c'est compliqué. Ceci est simplement une intuition. Le suivi de toutes ces informations au moment de l'exécution a un coût, après tout. Je ne suis pas convaincu par votre argumentation à propos d'IO. Si cela est prévisible (= cas d'utilisation typique), PGO peut le prédire. Si c'est faux, je ne suis pas convaincu que le JIT puisse l'optimiser non plus. Peut-être de temps en temps, par pure chance. Mais de manière fiable? …
Konrad Rudolph Le

2
@ Konrad: Votre intuition est fausse. Il ne s'agit pas de faire varier l'exécution, mais d' imprévisibilité au moment de la compilation . La solution idéale pour les JIT par rapport à l'optimisation statique n'est pas lorsque le comportement du programme change trop rapidement lors de l'exécution du profil, mais bien lorsque le comportement du programme est facile à optimiser dans chaque exécution du programme, mais varie énormément entre s'exécute. Un optimiseur statique ne doit généralement optimiser que pour un seul ensemble de conditions, alors qu'un JIT optimise chaque exécution séparément, en fonction des conditions rencontrées lors de cette exécution.
Ben

3
Remarque: ce fil de commentaires est en train de devenir une mini-salle de discussion. Si vous souhaitez poursuivre cette discussion, apportez-la s'il vous plaît. Les commentaires doivent être utilisés pour améliorer le post original. Veuillez interrompre cette conversation ici. Merci.
Robert Cartaino

20

si vous pouvez faire X en C / C ++ avec des performances Y, pouvez-vous faire X en Ruby / Python avec des performances proches de Y?

Oui. Prenons, par exemple, PyPy. C'est une collection de code Python qui exécute une interprétation proche de C (pas si proche, mais pas si loin non plus). Pour ce faire, il effectue une analyse complète du programme sur le code source afin d’attribuer un type statique à chaque variable (voir les documents Annotator et Rtyper pour plus de détails), puis, une fois doté des mêmes informations de type que C, il peut effectuer le même sortes d'optimisations. Au moins en théorie.

Le compromis est bien sûr que RPython n'accepte qu'un sous-ensemble de code Python et, en général, même si cette restriction est levée, seul un sous-ensemble de code Python peut fonctionner correctement: le sous-ensemble pouvant être analysé et doté de types statiques.

Si vous limitez suffisamment Python, vous pouvez créer des optimiseurs exploitant le sous-ensemble restreint et le compilant en code efficace. Ce n'est pas vraiment un avantage intéressant, en fait, c'est bien connu. Mais l’intérêt premier d’utiliser Python (ou Ruby) au départ était que nous voulions utiliser des fonctionnalités intéressantes qui analysent peut-être mal et donnent de bonnes performances! Donc, la question intéressante est en fait ...

En outre, les compilateurs JIT, tels que PyPy / Rubinius, seront-ils compatibles avec C / C ++ en termes de performances?

Nah.

Je veux dire par là: bien sûr, au fur et à mesure que le code s'accumule, vous pouvez obtenir suffisamment d'informations de frappe et suffisamment de zones réactives pour compiler l'intégralité du code en code machine. Et peut-être que nous pouvons obtenir que cela fonctionne mieux que C pour certains codes. Je ne pense pas que ce soit extrêmement controversé. Mais il doit encore "s'échauffer" et les performances sont encore un peu moins prévisibles. Elles ne seront pas aussi performantes que le C ou le C ++ pour certaines tâches nécessitant des performances élevées de manière constante et prévisible.

Les données de performances existantes pour Java, qui contiennent à la fois plus d'informations sur les types que Python ou Ruby, et un compilateur JIT mieux développé que Python ou Ruby, ne correspondent toujours pas à C / C ++. Il est cependant dans le même stade.


1
"Le compromis est bien sûr que seul un sous-ensemble de code Python est accepté, ou plutôt, seul un sous-ensemble de code Python peut bien fonctionner: le sous-ensemble qui peut être analysé et doté de types statiques." Ce n'est pas tout à fait exact. Seul le sous-ensemble peut être accepté du tout par le compilateur RPython. Compiler en RPython en C raisonnablement efficace ne fonctionne avec précision que parce que les parties difficiles à compiler de Python sont garanties de ne jamais se produire dans les programmes RPython; ce n'est pas simplement que s'ils se produisent, ils ne seront pas optimisés. C'est l'interprète compilé qui gère tout Python.
Ben

1
À propos de JIT: J'ai vu plus d'un benchmark où Java surclassait plusieurs versions de C (++). Seul le C ++ avec boost semble rester en avance de manière fiable. Dans ce cas, je m'interroge sur les performances par développeur, mais c'est un autre sujet.
Raphaël

@Ben: une fois que vous avez RPython, il est facile de créer un compilateur / interprète qui utilise l’interpréteur CPython lorsque le compilateur RPython échoue. Par conséquent, "seul un sous-ensemble de code Python peut bien fonctionner: ..." est totalement exact.
Lie Ryan

9
@Raphael Il a été démontré à maintes reprises que le code C ++ bien écrit surpasse Java. C'est la partie "bien écrite" qui est un peu plus difficile à obtenir en C ++, de sorte que dans de nombreux bancs, vous voyez les résultats que Java surpasse celui de C ++. C ++ est donc plus cher, mais lorsque vous avez besoin d’un contrôle précis de la mémoire et d’un grain de métal, vous vous adressez à C / C ++. C en particulier est juste un assembleur ac / p.
TC1

7
Comparer les performances maximales d'autres langages à des langages tels que C / C ++ est en quelque sorte un exercice futile, dans la mesure où vous pouvez assembler en ligne directement dans le cadre des spécifications du langage. Tout ce que la machine peut faire en exécutant un programme écrit dans n’importe quelle langue, vous pouvez au pire dupliquer en écrivant assembly à partir d’une trace d’instructions exécutées. Comme l'indique @Raphael dans un commentaire précédent, une mesure beaucoup plus intéressante serait la performance par effort de développement (heures-personnes, lignes de code, etc.).
Patrick87

18

La réponse courte est: nous ne savons pas , demandez à nouveau dans 100 ans. (Nous pourrions toujours ne pas savoir alors; peut-être que nous ne le saurons jamais.)

En théorie, c'est possible. Prenez tous les programmes jamais écrits, convertissez-les manuellement en code machine le plus efficace possible et écrivez un interpréteur qui mappe les codes sources en codes machines. Cela est possible car seul un nombre fini de programmes a été écrit (et au fur et à mesure que de nouveaux programmes sont écrits, maintenez les traductions manuelles). Ceci est aussi, bien sûr, complètement idiot sur le plan pratique.

Là encore, en théorie, les langages de haut niveau pourraient atteindre les performances du code machine, mais ils ne les dépasseront pas. C'est encore très théorique, car dans la pratique, nous avons très rarement recours à l'écriture de code machine. Cet argument ne s'applique pas à la comparaison de langages de niveau supérieur: cela n'implique pas que C doit être plus efficace que Python, seul ce code machine ne peut pas faire pire que Python.

Venant de l’autre côté, à titre expérimental, nous pouvons constater que la plupart du temps , les langages de haut niveau interprétés ont des performances pires que les langages de bas niveau compilés. Nous avons tendance à écrire du code non sensible au temps dans des langages de très haut niveau et des boucles internes à temps critique dans un assemblage, avec des langages comme C et Python se situant entre les deux. Bien que je ne dispose d'aucune statistique à ce sujet, je pense que c'est la meilleure décision dans la plupart des cas.

Cependant, il existe des cas non contestés où les langages de haut niveau surpassent le code que l'on rédigerait de manière réaliste: les environnements de programmation spéciaux. Des programmes comme Matlab et Mathematica sont souvent bien meilleurs pour résoudre certains types de problèmes mathématiques que ce que de simples mortels peuvent écrire. Les fonctions de la bibliothèque ont peut-être été écrites en C ou C ++ (ce qui alimente le camp «Les langages de bas niveau sont plus efficaces»), mais ça ne me regarde pas si j'écris du code Mathematica, la bibliothèque est une boîte noire.

Est-il théoriquement possible que Python soit aussi proche, voire plus proche, de performances optimales que C? Comme on l'a vu ci-dessus, oui, mais nous en sommes très loin aujourd'hui. Là encore, les compilateurs ont fait beaucoup de progrès au cours des dernières décennies, et ces progrès ne ralentissent pas.

Les langages de haut niveau ont tendance à automatiser davantage les tâches. Ils ont donc plus de travail à effectuer et ont donc tendance à être moins efficaces. D'un autre côté, ils ont tendance à avoir plus d'informations sémantiques, il est donc plus facile de repérer les optimisations (si vous écrivez un compilateur Haskell, vous n'avez pas à craindre qu'un autre thread modifie une variable sous votre nez). L'un des nombreux efforts visant à comparer des pommes et des oranges avec différents langages de programmation est le jeu de tests de langage informatique (anciennement connu sous le nom de fusillade). Fortran a tendance à briller lors de tâches numériques; mais quand il s'agit de manipuler des données structurées ou une commutation de thread à haut débit, F # et Scala se débrouillent bien. Ne prenez pas ces résultats comme des évangiles: une grande partie de ce qu'ils mesurent est la qualité de l'auteur du programme de test dans chaque langue.

Un argument en faveur des langages de haut niveau est que la performance sur les systèmes modernes n’est pas aussi fortement corrélée au nombre d’instructions exécutées, et moins au fil du temps. Les langages de bas niveau conviennent parfaitement aux machines séquentielles simples. Si un langage de haut niveau exécute deux fois plus d'instructions, mais parvient à utiliser le cache de manière plus intelligente, de sorte qu'il en divise par deux le nombre d'omissions manquantes dans le cache, le vainqueur peut en résulter.

Sur les plates-formes de serveur et de bureau, les processeurs ont presque atteint un plateau où ils ne vont pas plus vite (les plates-formes mobiles y parviennent aussi); cela favorise les langues où le parallélisme est facile à exploiter. Beaucoup de processeurs passent le plus clair de leur temps à attendre une réponse d’entrée / sortie; le temps passé en calcul importe peu par rapport à la quantité d'E / S, et un langage permettant au programmeur de minimiser les communications est un avantage.

Dans l’ensemble, si les langues de haut niveau commencent par une pénalité, elles ont plus de marge de progression. À quel point peuvent-ils se rapprocher? Demander à nouveau dans 100 ans.

Note finale: souvent, la comparaison n’est pas entre le programme le plus efficace pouvant être écrit en langue A et le même dans le langage B, ni entre le programme le plus efficace jamais écrit dans chaque langue, mais entre le programme le plus efficace qui puisse être écrit par un humain dans un certain temps dans chaque langue. Cela introduit un élément qui ne peut pas être analysé mathématiquement, même en principe. Concrètement, cela signifie souvent que la meilleure performance est un compromis entre le niveau de code de bas niveau que vous devez écrire pour atteindre les objectifs de performance et le temps que vous avez le temps d'écrire pour respecter les dates de parution.


Je pense que la distinction entre haut niveau et bas niveau n'est pas la bonne. C ++ peut être (très) de haut niveau. Pourtant, le C ++ moderne de haut niveau ne fonctionne pas (nécessairement) moins bien que son équivalent de bas niveau, bien au contraire. C ++ et ses bibliothèques ont été soigneusement conçus pour offrir des abstractions de haut niveau sans nuire aux performances. Il en va de même pour votre exemple Haskell: ses abstractions de haut niveau permettent souvent plutôt que d'empêcher les optimisations. La distinction originale entre les langages dynamiques et les langages statiques est plus logique à cet égard.
Konrad Rudolph

@ KonradRudolph Vous avez raison, cette distinction entre niveau bas et niveau élevé constitue une distinction quelque peu arbitraire. Mais les langages dynamiques vs statiques ne capturent pas tout non plus; une équipe commune d'enquête peut éliminer une grande partie de la différence. Essentiellement, les réponses théoriques connues à cette question sont triviales et inutiles, et la réponse pratique est «ça dépend».
Gilles 'SO- arrête d'être méchant'

Dans ce cas, je pense que la question devient simplement «à quel point les EJI peuvent-ils devenir bons, et s’ils dépassent la compilation statique, les langages compilés statiquement peuvent-ils aussi en tirer profit? Et oui, je suis d’accord avec votre évaluation mais nous pouvons sûrement obtenir des suppositions informées qui vont au-delà de «ça dépend». ;-)
Konrad Rudolph

@ KonradRudolph Si je voulais deviner, je poserais des questions sur le génie logiciel .
Gilles 'SO- arrête d'être méchant'

1
La fusillade linguistique est malheureusement une source discutable de points de repère quantitatifs: ils n'acceptent pas tous les programmes, mais uniquement ceux réputés typiques de la langue. C'est une exigence délicate et très subjective. cela signifie que vous ne pouvez pas supposer qu'une implémentation en fusillade est réellement une bonne chose (et dans la pratique, certaines implémentations ont de toute évidence des alternatives supérieures à celles rejetées). D'un autre côté; Ce sont des microbiores et certaines implémentations utilisent des techniques inhabituelles que vous n'auriez jamais envisagées dans un scénario plus normal, simplement pour gagner en performance. Donc: c'est un jeu, pas une source de données très fiable.
Eamon Nerbonne le

10

La différence fondamentale entre la déclaration C ++ x = a + bet l'instruction Python x = a + best qu'un compilateur C / C ++ peut dire de cette déclaration (et un peu d' informations supplémentaires qu'il a facilement disponible sur les types de x, aet b) précisément ce que le code machine doit être exécutée . Tandis que pour dire quelles opérations l’instruction Python va effectuer, vous devez résoudre le problème Halting.

En C, cette instruction compilera essentiellement l'un des quelques types d'ajout de machine (et le compilateur C sait lequel). En C ++, cette méthode peut être compilée ou appelée à appeler une fonction connue de manière statique, ou (dans le pire des cas), elle doit peut-être être compilée en une méthode de recherche et d'appel virtuelle, même si celle-ci entraîne une surcharge de code machine assez réduite. Plus important encore, le compilateur C ++ peut dire, à partir des types connus statiquement impliqués, s'il peut émettre une seule opération d'addition rapide ou s'il doit utiliser l'une des options plus lentes.

En Python, un compilateur pourrait théoriquement faire presque ce bien si elle savait que aet bétaient tous les deux ints. Il y a une surcharge de boxe supplémentaire, mais si les types étaient connus statiquement, vous pourriez probablement vous en débarrasser aussi (tout en présentant l'interface, les entiers sont des objets avec des méthodes, une hiérarchie de super-classes, etc.). Le problème est un compilateur pour Python ne peut passachez cela, car les classes sont définies à l'exécution, peuvent être modifiées à l'exécution, et même les modules qui définissent et importent sont résolus à l'exécution (et même les instructions d'importation exécutées dépendent d'éléments connus uniquement lors de l'exécution). Le compilateur Python devrait donc savoir quel code a été exécuté (c'est-à-dire résoudre le problème de l'arrêt) afin de savoir ce que l'instruction en cours de compilation fera.

Ainsi, même avec les analyses les plus sophistiquées qui sont théoriquement possibles , vous ne pouvez tout simplement pas en dire beaucoup sur ce qu’une déclaration Python va faire à l’avance. Cela signifie que même si un compilateur Python sophistiqué était implémenté, il devrait toujours émettre du code machine conforme au protocole de recherche dans le dictionnaire Python pour déterminer la classe d'un objet et trouver des méthodes (passant par le MRO de la hiérarchie de classes). qui peut aussi changer de façon dynamique au moment de l’exécution et est donc difficile à compiler dans une simple table de méthode virtuelle) et fait essentiellement ce que font les interprètes (lents). C'est pourquoi il n'existe pas vraiment de compilateurs d'optimisation sophistiqués pour les langages dynamiques. Ce n'est pas simplement difficile d'en créer un, le gain maximum possible n'est pas

Notez que cela ne repose pas sur ce que le code est fait, il est basé sur ce que le code pourrait faire. Même le code Python, qui est une simple série d'opérations arithmétiques sur nombres entiers, doit être compilé comme s'il appelait des opérations de classe arbitraires. Les langages statiques imposent de plus grandes restrictions quant aux possibilités d'utilisation du code et, par conséquent, leurs compilateurs peuvent faire davantage d'hypothèses.

Les compilateurs JIT gagnent sur ce point en attendant le moment de l'exécution pour compiler / optimiser. Cela leur permet d' émettre un code qui fonctionne pour ce que le code est en train de faire plutôt que ce qu'il pourrait faire. Et à cause de cela, les compilateurs JIT ont un potentiel de gain beaucoup plus important pour les langages dynamiques que pour les langages statiques; pour des langages plus statiques, une bonne partie de ce qu'un optimiseur aimerait savoir peut être connue à l'avance. Vous pouvez donc aussi l'optimiser, laissant ainsi moins de choses à un compilateur JIT.

Il existe différents compilateurs JIT pour langages dynamiques qui prétendent atteindre des vitesses d’exécution comparables à celles du C / C ++ compilé et optimisé. Il existe même des optimisations pouvant être effectuées par un compilateur JIT qui ne peuvent pas être effectuées par un compilateur en avance pour n'importe quel langage. Ainsi, en théorie, la compilation JIT (pour certains programmes) pourrait un jour dépasser le meilleur compilateur statique possible. Mais comme Devin l'a fait remarquer à juste titre, les propriétés de la compilation JIT (seuls les "hotspots" sont rapides et uniquement après une période de préchauffage) signifient qu'il est peu probable que les langages dynamiques compilés par JIT soient adaptés à toutes les applications possibles, même s'ils deviennent aussi vite ou plus rapidement que les langages compilés statiquement en général.


1
Cela fait maintenant deux votes négatifs sans commentaires. Je serais heureux de recevoir des suggestions pour améliorer cette réponse!
Ben

Je n'ai pas voté vers le bas, mais vous avez tort à propos de "la nécessité de résoudre le problème de blocage". Il a été démontré dans de nombreuses circonstances que le code dans les langages dynamiques peut être compilé pour un code cible optimal, alors qu'à ma connaissance, aucune de ces démonstrations ne comportait de solution au problème
persistant

@ Mikera Je suis désolé, mais non, vous avez tort. Personne n'a jamais implémenté de compilateur (au sens où nous comprenons que GCC est un compilateur) pour un langage Python complètement général ou un autre langage dynamique. Chacun de ces systèmes ne fonctionne que pour un sous-ensemble du langage ou pour certains programmes, ou émet parfois un code qui est essentiellement un interpréteur contenant un programme codé en dur. Si vous le souhaitez, je vais vous écrire un programme Python contenant la ligne foo = x + yoù la prédiction du comportement de l'opérateur d'addition lors de la compilation dépend de la résolution du problème d'arrêt.
Ben

Je suis correct et je pense que vous manquez le point. J'ai dit "dans de nombreuses circonstances". Je n'ai pas dit "dans toutes les circonstances possibles". Que vous puissiez ou non construire un exemple artificiel lié au problème persistant est en grande partie hors de propos dans le monde réel. FWIW, vous pouvez également construire un exemple similaire pour C ++ afin de ne rien prouver de toute façon. En tout cas, je ne suis pas venu ici pour me disputer, mais simplement pour suggérer des améliorations à votre réponse. À prendre ou a laisser.
Mikera

@ Mikea Je pense que vous pourriez manquer le point. Pour pouvoir effectuer x + ydes opérations d’ajout de machine efficaces, vous devez savoir au moment de la compilation s’il s’agit ou non. Tout le temps , pas seulement de temps en temps. Pour les langages dynamiques, cela n’est presque jamais possible avec des programmes réalistes, même si une heuristique raisonnable suppose une réponse exacte la plupart du temps. La compilation nécessite des garanties lors de la compilation . Donc, en parlant de "dans de nombreuses circonstances", vous ne répondez pas du tout à ma réponse.
Ben

9

Juste un pointeur rapide décrivant le pire scénario pour les langages dynamiques:

L'analyse Perl n'est pas calculable

En conséquence, Perl (complet) ne peut jamais être compilé de manière statique.


En général, comme toujours, cela dépend. Je suis convaincu que si vous essayez d'émuler des caractéristiques dynamiques dans un langage compilé de manière statique, des interpréteurs bien conçus ou des variantes (partiellement) compilées peuvent se rapprocher ou saper les performances des langages compilés de manière statique.

Un autre point à garder à l'esprit est que les langages dynamiques résolvent un autre problème que le C. Le C est à peine une belle syntaxe pour l'assembleur, alors que les langages dynamiques offrent des abstractions riches. Les performances d'exécution ne sont souvent pas la principale préoccupation: le délai de mise sur le marché, par exemple, dépend de la capacité de vos développeurs à écrire des systèmes complexes et de grande qualité dans des délais très courts. L'extensibilité sans recompilation, par exemple avec des plugins, est une autre fonctionnalité populaire. Quelle langue préférez-vous dans ces cas?


5

Pour tenter d'apporter une réponse plus objectivement scientifique à cette question, je discute comme suit. Un langage dynamique nécessite un interprète, ou une exécution, pour prendre des décisions au moment de l'exécution. Cet interpréteur, ou exécution, est un programme informatique et, en tant que tel, a été écrit dans un langage de programmation statique ou dynamique.

Si l'interprète / le moteur d'exécution est écrit dans un langage statique, vous pouvez alors écrire un programme dans ce langage statique qui (a) remplit la même fonction que le programme dynamique qu'il interprète et (b) effectue au moins aussi bien. Espérons que cela va de soi, car fournir une preuve rigoureuse de ces affirmations nécessiterait des efforts supplémentaires (voire considérables).

En supposant que ces affirmations soient vraies, la seule solution consiste à exiger que l'interpréteur / runtime soit également écrit dans un langage dynamique. Cependant, nous rencontrons le même problème qu'auparavant: si l'interpréteur est dynamique, il nécessite un interpréteur / runtime, qui doit également avoir été écrit dans un langage de programmation, dynamique ou statique.

Sauf si vous supposez qu'une instance d'un interprète est capable de s'interpréter elle-même à l'exécution (j'espère que cela est évidemment absurde), le seul moyen de vaincre les langues statiques est que chaque instance d'interprète soit interprétée par une instance distincte d'interprète; cela mène soit à une régression infinie (j'espère que cela est évidemment absurde), soit à une boucle d'interprètes fermée (j'espère que cela est également évidemment absurde).

Il semble donc que même en théorie, les langages dynamiques ne fonctionnent pas mieux que les langages statiques en général. Lorsque vous utilisez des modèles d’ordinateurs réalistes, cela semble encore plus plausible; Après tout, une machine ne peut exécuter que des séquences d'instructions machine et toutes les séquences d'instructions machine peuvent être compilées de manière statique.

En pratique, pour faire correspondre les performances d'un langage dynamique à un langage statique, il peut être nécessaire de réimplémenter l'interpréteur / runtime dans un langage statique. Cependant, le fait que vous puissiez le faire est l’essentiel de cet argument. C'est une question de poule et d'oeuf et, si vous êtes d'accord avec les hypothèses non prouvées (bien que, à mon avis, la plupart du temps évidentes d'elles-mêmes) faites ci-dessus, nous pouvons réellement y répondre; nous devons donner le feu vert aux langages statiques et non dynamiques.

Une autre façon de répondre à la question, à la lumière de cette discussion, est la suivante: dans le modèle programmé à calcul stocké, control = data, qui est au cœur de l’informatique moderne, la distinction entre compilation statique et compilation dynamique est une fausse dichotomie; Les langages compilés statiquement doivent avoir un moyen de générer et d’exécuter du code arbitraire au moment de l’exécution. C'est fondamentalement lié au calcul universel.


En relisant ceci, je ne pense pas que ce soit vrai. La compilation JIT rompt votre argument. Même le code le plus simple, par exemple, main(args) { for ( i=0; i<1000000; i++ ) { if ( args[0] == "1" ) {...} else {...} }peut être considérablement accéléré une fois la valeur de argsconnue (en supposant qu'il ne change jamais, ce que nous pourrons peut-être affirmer). Un compilateur statique ne peut pas créer de code qui supprime la comparaison. (Bien sûr, dans cet exemple, vous sortez simplement ifde la boucle. Mais la chose peut être plus compliquée.)
Raphael

@ Raphaël Je pense que JIT fait probablement mon argumentation. Les programmes qui effectuent une compilation JIT (par exemple, JVM) sont généralement des programmes compilés statiquement. Si un programme JIT compilé statiquement peut exécuter un script plus rapidement qu'un autre programme statique peut effectuer le même travail, il vous suffit de "lier" le script avec le compilateur JIT et d'appeler le paquet un programme compilé statiquement. Cela doit faire au moins aussi bien que le JIT opérant sur un programme dynamique séparé, contredisant tout argument selon lequel le JIT doit faire mieux.
Patrick87

Hm, cela revient à dire que l'intégration d'un script Ruby avec son interpréteur vous donne un programme compilé de manière statique. Je ne suis pas d'accord (puisqu'il supprime toutes les distinctions de langues à cet égard), mais c'est une question de sémantique, pas de concepts. D'un point de vue conceptuel, l'adaptation du programme à l'exécution (JIT) peut faire des optimisations impossibles à la compilation (compilateur statique), et c'est ce que je veux dire.
Raphaël

@Raphael Le fait qu'il n'y ait pas de distinction significative est en quelque sorte le but de la réponse: toute tentative de classification rigide de certaines langues comme statiques, et donc souffrant de limitations de performances, échoue pour la même raison: il n'y a pas de différence convaincante entre un préemballé (Ruby , script) bundle et un programme C. Si (Ruby, script) peut faire en sorte que la machine exécute la bonne séquence d'instructions pour résoudre efficacement un problème donné, un programme C astucieusement conçu pourrait le faire.
Patrick87

Mais vous pouvez définir la différence. Une variante envoie le code disponible au processeur sous forme inchangée (C), l'autre est compilée au moment de l'exécution (Ruby, Java, ...). Le premier est ce que nous entendons par "compilation statique" tandis que le dernier serait "compilation juste à temps" (qui permet des optimisations dépendantes des données).
Raphaël

4

Pouvez-vous créer des compilateurs pour des langages dynamiques tels que Ruby afin d’avoir des performances similaires et comparables à celles du C / C ++?

Je pense que la réponse est "oui" . Je pense aussi qu’ils peuvent même dépasser l’architecture actuelle C / C ++ en termes d’efficacité (même légèrement).

La raison est simple: il y a plus d'informations à l'exécution qu'à la compilation.

Les types dynamiques ne sont qu'un léger obstacle: si une fonction est toujours ou presque toujours exécutée avec les mêmes types d'arguments, un optimiseur JIT peut générer une branche et un code machine pour ce cas spécifique. Et il y a tellement plus qui peut être fait.

Voir Dynamic Languages ​​Strike Back , un discours de Steve Yegge de Google (il existe également une version vidéo quelque part, je crois). Il mentionne des techniques concrètes d'optimisation JIT de la V8. Inspirant!

J'attends avec impatience ce que nous aurons dans les 5 prochaines années!


2
J'aime l'optimisme.
Dave Clarke

Je crois qu'il y a eu des critiques très précises concernant les inexactitudes dans le discours de Steve. Je les posterai quand je les trouverai.
Konrad Rudolph

1
@DaveClarke c'est ce qui me fait courir :)
Kos

2

Les personnes qui pensent que cela est théoriquement possible, ou dans un futur lointain, ont complètement tort, à mon avis. Le problème réside dans le fait que les langages dynamiques fournissent et imposent un style de programmation totalement différent. En réalité, la différence est double, même si les deux aspects sont liés:

  • Les symboles (vars, ou plutôt id <-> liaisons de données de toutes sortes) ne sont pas typés.
  • Les structures (les données, tout ce qui vit à l'exécution) sont également non typées par les types de leurs éléments.

Le deuxième point fournit la généricité gratuitement. Notez que les structures ici sont des éléments composites, des collections, mais aussi des types eux-mêmes, et même (!) Des routines de toutes sortes (fonctions, actions, opérations) ... Nous pourrions taper des structures par leurs types d'élément, mais en raison du premier point vérifier aurait lieu à l'exécution de toute façon. Nous aurions pu taper des symboles tout en conservant ceux structurés sans les typer en fonction de leurs types d’éléments (un tableau aserait simplement typé comme un tableau et non comme un tableau d’entiers), mais même cela n’est pas vrai dans un langage dynamique ( apourrait aussi bien contenir un string).

L

  • ElementLL
  • ElementL
  • toutes les structures (encore une fois, y compris les routines du modèle) reçoivent uniquement les éléments

Il est clair pour moi qu’il s’agit là d’une pénalité énorme. et je ne touche même pas à toutes les conséquences (la myriade de vérifications d'exécution de toutes sortes nécessaires pour assurer la sensibilité du programme) bien décrites dans d'autres publications.


+1 très intéressant. Avez-vous lu ma réponse? Votre pensée et moi paraissent similaires, bien que votre réponse fournisse plus de détails et une perspective intéressante.
Patrick87

Les langages dynamiques ne doivent pas nécessairement être non typés. L'implémentation d'un modèle de langage dynamique en C limite fortement les possibilités d'optimisation; il est très difficile pour le compilateur de reconnaître les optimisations de haut niveau (par exemple, des données immuables) et de forcer certaines opérations fondamentales (par exemple, les appels de fonction) à passer par C. Ce que vous décrivez n'est pas loin d'un interpréteur de code octet, avec le décodage de code octet pré-évalué; Les compilateurs natifs ont tendance à avoir des performances nettement meilleures. Je doute que le décodage de bytecode puisse justifier la différence.
Gilles 'SO- arrête d'être méchant'

@ Patrick87: vous avez raison, nos réflexions semblent très similaires (je n'avais pas lu auparavant, désolée, ma réflexion vient de la mise en œuvre actuelle d'un dyn lang en C).
spir

@ Gilles: Je suis plutôt d'accord sur le point "... ne devez pas être non typé", si vous voulez dire non typé statiquement . Mais ce n’est pas ce que les gens pensent de dyn langs en général, je suppose. Je considère personnellement que la généricité (au sens général donné dans la réponse ci-dessus) est une caractéristique beaucoup plus puissante et beaucoup plus difficile à vivre sans. Nous pouvons facilement trouver des solutions aux types statiques en élargissant notre réflexion sur la manière de définir un type donné (apparemment polymorphe) ou en donnant plus de flexibilité aux instances directement.
Spir

1

Je n'ai pas eu le temps de lire toutes les réponses en détail ... mais j'étais amusé.

Une controverse similaire a eu lieu dans les années soixante et au début des années soixante-dix (l'histoire de l'informatique se répète souvent): peut-on compiler des langages de haut niveau pour produire un code aussi efficace que le code machine, disons le code assembleur, produit manuellement par un programmeur. Tout le monde sait qu'un programmeur est beaucoup plus intelligent que n'importe quel programme et peut proposer une optimisation très intelligente (en pensant principalement à ce qu'on appelle maintenant l'optimisation du judas). C'est bien sûr ironie de ma part.

Il y avait même un concept d'expansion du code: le rapport entre la taille du code produit par un compilateur et la taille du code du même programme produit par un bon programmeur (comme s'il y en avait eu trop :-). Bien entendu, l'idée était que ce rapport était toujours supérieur à 1. Les langues de l'époque étaient le cobol et le fortran 4, ou l'algol 60 pour les intellectuels. Je crois que Lisp n'a pas été considéré.

Il y avait des rumeurs selon lesquelles quelqu'un aurait produit un compilateur pouvant parfois obtenir un taux d'expansion égal à 1 ... jusqu'à ce qu'il devienne simplement la règle voulant que le code compilé soit bien meilleur que le code écrit à la main (et plus fiable également). Les gens étaient inquiets à propos de la taille du code à cette époque (petits souvenirs), mais il en va de même pour la vitesse ou la consommation d’énergie. Je ne vais pas entrer dans les raisons.

Les fonctionnalités étranges, les fonctionnalités dynamiques d'une langue importent peu. Ce qui compte, c'est la manière dont ils sont utilisés, qu'ils soient utilisés ou non. Les performances, quelle que soit l’unité (taille du code, vitesse, énergie, ...) dépendent souvent de très petites parties des programmes. Il y a donc de fortes chances pour que les installations qui donnent un pouvoir d'expression ne gênent pas vraiment. Avec de bonnes pratiques de programmation, les installations de pointe ne sont utilisées que de manière disciplinée, pour imaginer de nouvelles structures (c'était la leçon à tirer).

Le fait qu'une langue ne comporte pas de typage statique n'a jamais signifié que les programmes écrits dans cette langue ne sont pas typés de manière statique. D'autre part, il se peut que le système de types utilisé par un programme ne soit pas encore suffisamment formalisé pour qu'un vérificateur de types existe maintenant.

Au cours de la discussion, il y a eu plusieurs références à l'analyse du cas le plus défavorable ("problème stoppant", analyse PERL). Mais l'analyse du cas le plus défavorable n'est généralement pas pertinente. Ce qui compte, c’est ce qui se passe dans la plupart des cas ou dans des cas utiles… qu’ils soient définis, compris ou vécus. Voici une autre histoire, directement liée à l'optimisation du programme. Cela s'est passé il y a longtemps dans une grande université du Texas, entre un doctorant et son conseiller (qui a ensuite été élu dans l'une des académies nationales). Si je me souviens bien, l’élève insistait pour étudier un problème d’analyse / optimisation que le conseiller avait montré impossible à résoudre. Bientôt, ils ne parlèrent plus. Mais l'étudiant avait raison: le problème était suffisamment facile à résoudre dans la plupart des cas pratiques pour que la thèse qu'il a produite devienne un ouvrage de référence.

Et pour commenter davantage la déclaration selon laquelle Perl parsing is not computable, quelle que soit la signification de cette phrase, il existe un problème similaire avec ML, qui est un langage remarquablement bien formalisé. Type checking complexity in ML is a double exponential in the lenght of the program.C’est un résultat très précis et formel dans le pire des cas, ce qui n’a aucune importance. Après tout, les utilisateurs de ML attendent toujours un programme pratique qui fera exploser le vérificateur de type.

Dans de nombreux cas, comme auparavant, le temps et la compétence de l'homme sont plus rares que la puissance de calcul.

Le vrai problème du futur sera de faire évoluer nos langages pour intégrer de nouvelles connaissances, de nouvelles formes de programmation, sans avoir à réécrire tous les logiciels existants qui sont encore utilisés.

Si vous regardez les mathématiques, c'est un très grand corpus de connaissances. Les langages utilisés pour l'exprimer, les notations et les concepts ont évolué au fil des siècles. Il est facile d’écrire d’anciens théorèmes avec les nouveaux concepts. Nous adaptons les preuves principales, mais ne vous inquiétez pas pour beaucoup de résultats.

Mais dans le cas de la programmation, il se peut que nous devions réécrire toutes les preuves à partir de zéro (les programmes sont des preuves). Peut-être avons-nous vraiment besoin de langages de programmation de très haut niveau et évolutifs. Les concepteurs d'optimisation seront heureux de suivre.


0

Quelques notes:

  • Tous les langages de haut niveau ne sont pas dynamiques. Haskell est de très haut niveau, mais est entièrement typé de manière statique. Même les langages de programmation tels que Rust, Nim et D peuvent exprimer des abstractions de haut niveau de manière succincte et efficace. En fait, ils peuvent être aussi concis que des langages dynamiques.

  • Il existe des compilateurs extrêmement optimisés pour les langages dynamiques. Les implémentations Good Lisp atteignent la moitié de la vitesse de l’équivalent C.

  • La compilation JIT peut être une grande victoire ici. Le pare-feu d'applications Web de CloudFlare génère du code Lua exécuté par LuaJIT. LuaJIT optimise fortement les chemins d’exécution réellement empruntés (en général, les chemins non attaquants), de sorte que le code s’exécute beaucoup plus rapidement que le code produit par un compilateur statique sur la charge de travail réelle. Contrairement à un compilateur statique avec optimisation guidée par le profil, LuaJIT s’adapte aux modifications des chemins d’exécution lors de l’exécution.

  • La désoptimisation est également cruciale. Au lieu que le code compilé par JIT ait besoin de vérifier si une classe est monkeypatchée, l'acte monkeypatching déclenche un crochet dans le système d'exécution qui supprime le code machine qui reposait sur l'ancienne définition.


Comment est-ce une réponse? Eh bien, la puce trois peut-être, si vous avez ajouté des références.
Raphaël

Je suis très sceptique quant à l'affirmation selon laquelle PGO ne pourrait pas égaler les performances de LuaJIT pour le code d'application Web sous une charge de travail typique.
Konrad Rudolph

@ KonradRudolph Le principal avantage d'un JIT est que celui-ci adapte le code lorsque différents chemins deviennent chauds.
Demi

@Demetri je le sais. Mais il est très difficile de quantifier s’il s’agit là d’un avantage - voir ma réponse et la discussion commentée ici. En résumé: le JIT peut s’adapter aux changements d’utilisation, mais il doit également effectuer le suivi des éléments au moment de l’exécution, ce qui entraîne une surcharge. Le seuil de rentabilité pour cela n’est intuitif que lorsque de fréquents changements de comportement se produisent. Pour les applications Web, il n’ya probablement qu’un seul (ou très peu) modèle d’utilisation pour lequel l’optimisation est rentable. Ainsi, l’augmentation minimale des performances due à l’adaptabilité ne compense pas les frais généraux liés au profilage continu.
Konrad Rudolph
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.