web-dev-qa-db-fra.com

Pourquoi le dépassement arithmétique est-il ignoré?

Avez-vous déjà essayé de résumer tous les nombres de 1 à 2 000 000 dans votre langage de programmation préféré? Le résultat est facile à calculer manuellement: 2 000 001 000 000, soit environ 900 fois plus que la valeur maximale d'un entier 32 bits non signé.

C # imprime -1453759936 - une valeur négative! Et je suppose que Java fait la même chose.

Cela signifie qu'il existe des langages de programmation communs qui ignorent le débordement arithmétique par défaut (en C #, il existe des options cachées pour changer cela). C'est un comportement qui me semble très risqué, et le crash d'Ariane 5 n'a-t-il pas été provoqué par un tel débordement?

Alors: quelles sont les décisions de conception derrière un comportement aussi dangereux?

Éditer:

Les premières réponses à cette question expriment les coûts excessifs du contrôle. Exécutons un court programme C # pour tester cette hypothèse:

Stopwatch watch = Stopwatch.StartNew();
checked
{
    for (int i = 0; i < 200000; i++)
    {
        int sum = 0;
        for (int j = 1; j < 50000; j++)
        {
            sum += j;
        }
    }
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);

Sur ma machine, la version cochée prend 11015 ms, tandis que la version non cochée prend 4125 ms. C'est à dire. les étapes de vérification prennent presque deux fois plus de temps que l'ajout des chiffres (au total 3 fois l'heure d'origine). Mais avec les 10 000 000 000 de répétitions, le temps pris par un chèque est toujours inférieur à 1 nanoseconde. Il peut y avoir une situation où cela est important, mais pour la plupart des applications, cela n'a pas d'importance.

Modifier 2:

J'ai recompilé notre application serveur (un service Windows analysant les données reçues de plusieurs capteurs, un certain nombre de calculs impliqués) avec le /p:CheckForOverflowUnderflow="false" paramètre (normalement, j'active la vérification de débordement) et le déploie sur un appareil. La surveillance de Nagios montre que la charge CPU moyenne est restée à 17%.

Cela signifie que l'atteinte de performances trouvée dans l'exemple composé ci-dessus est totalement hors de propos pour notre application.

77
Bernhard Hiller

Il y a 3 raisons à cela:

  1. Le coût de la vérification des débordements (pour chaque opération arithmétique unique) au moment de l'exécution est excessif.

  2. La complexité de prouver qu'un contrôle de dépassement de capacité peut être omis au moment de la compilation est excessive.

  3. Dans certains cas (par exemple, calculs CRC, bibliothèques de grands nombres, etc.), le "débordement automatique" est plus pratique pour les programmeurs.

86
Brendan

Qui a dit que c'était un mauvais compromis?!

J'exécute toutes mes applications de production avec la vérification du débordement activée. Il s'agit d'une option du compilateur C #. En fait, j'ai comparé cela et je n'ai pas pu déterminer la différence. Le coût d'accès à la base de données pour générer du HTML (non jouet) éclipse les coûts de vérification du débordement.

J'apprécie le fait que je sais qu'aucune opération ne déborde en production. Presque tout le code se comporterait de manière erratique en présence de débordements. Les bugs ne seraient pas bénins. La corruption des données est probable, des problèmes de sécurité sont possibles.

Dans le cas où j'ai besoin des performances, ce qui est parfois le cas, je désactive la vérification du débordement à l'aide de unchecked {} sur une base granulaire. Quand je veux dire que je compte sur une opération qui ne déborde pas, je pourrais ajouter de manière redondante checked {} au code pour documenter ce fait. Je suis attentif aux débordements mais je n'ai pas forcément besoin de l'être grâce à la vérification.

Je crois que l'équipe C # a fait le mauvais choix lorsqu'elle a choisi de ne pas vérifier le débordement par défaut, mais ce choix est maintenant scellé en raison de graves problèmes de compatibilité. Notez que ce choix a été fait vers l'an 2000. Le matériel était moins performant et .NET n'avait pas encore beaucoup de traction. Peut-être que .NET voulait faire appel à Java et les programmeurs C/C++ de cette manière. .NET est également censé être proche du métal. C'est pourquoi il a du code, des structures et des grandes capacités d'appels natifs qui tous Java n'a pas.

Plus notre matériel est rapide et plus les compilateurs sont intelligents, plus le contrôle de débordement par défaut est attrayant.

Je crois également que la vérification du débordement est souvent meilleure que les nombres de taille infinie. Les nombres infinis ont un coût de performance encore plus élevé, plus difficile à optimiser (je crois) et ils ouvrent la possibilité d'une consommation de ressources illimitée.

La façon dont JavaScript traite les débordements est encore pire. Les nombres JavaScript sont des doubles à virgule flottante. Un "débordement" se manifeste comme laissant l'ensemble d'entiers entièrement précis. Légèrement des résultats incorrects se produiront (comme être désactivés par un - cela peut transformer des boucles finies en boucles infinies).

Pour certains langages tels que le contrôle de débordement C/C++ par défaut, il est clairement inapproprié car les types d'applications qui sont écrites dans ces langages nécessitent des performances nues. Néanmoins, des efforts sont déployés pour faire de C/C++ un langage plus sûr en permettant d'activer un mode plus sûr. C'est louable car 90 à 99% du code a tendance à être froid. Un exemple est l'option de compilateur fwrapv qui force le complément à 2. Il s'agit d'une fonctionnalité de "qualité d'implémentation" du compilateur, et non du langage.

Haskell n'a pas de pile d'appels logiques et aucun ordre d'évaluation spécifié. Cela fait que des exceptions se produisent à des points imprévisibles. Dans a + b il n'est pas spécifié si a ou b est évalué en premier et si ces expressions se terminent ou non. Par conséquent, il est logique que Haskell utilise la plupart du temps des entiers non bornés. Ce choix convient à un langage purement fonctionnel car les exceptions sont vraiment inappropriées dans la plupart des codes Haskell. Et la division par zéro est en effet un point problématique dans la conception du langage Haskells. Au lieu d'entiers illimités, ils auraient pu également utiliser des entiers enveloppants à largeur fixe, mais cela ne correspond pas au thème "focus sur l'exactitude" proposé par le langage.

Une alternative aux exceptions de débordement sont les valeurs empoisonnées qui sont créées par des opérations non définies et se propagent à travers les opérations (comme la valeur float NaN). Cela semble beaucoup plus cher que la vérification de débordement et rend toutes les opérations plus lentes, pas seulement celles qui peuvent échouer (sauf l'accélération matérielle qui a généralement et les entrées n'ont généralement pas - bien que Itanium a NaT qui est "Pas une chose" " ). Je ne vois pas non plus l'intérêt de faire en sorte que le programme continue à boiter avec de mauvaises données. C'est comme ON ERROR RESUME NEXT. Il masque les erreurs mais n'aide pas à obtenir des résultats corrects. supercat souligne que c'est parfois une optimisation des performances pour ce faire.

64
usr

Parce que c'est un mauvais compromis de faire des calculs tous beaucoup plus chers afin de détecter automatiquement le rare cas où un débordement se produit se produit. Il est préférable de charger le programmeur de reconnaître les rares cas où cela pose un problème et d'ajouter des empêchements spéciaux que de faire en sorte que les programmeurs tous paient le prix des fonctionnalités qu'ils n'utilisent pas.

30
Kilian Foth

quelles sont les décisions de conception derrière un comportement aussi dangereux?

"N'obligez pas les utilisateurs à payer une pénalité de performances pour une fonctionnalité dont ils n'ont peut-être pas besoin."

C'est l'un des principes les plus élémentaires de la conception de C et C++, et découle d'une époque différente où vous avez dû passer par des contorsions ridicules pour obtenir des performances à peine suffisantes pour des tâches qui sont aujourd'hui considérées comme triviales.

Les langages plus récents rompent avec cette attitude pour de nombreuses autres fonctionnalités, telles que la vérification des limites des tableaux. Je ne sais pas pourquoi ils ne l'ont pas fait pour la vérification du débordement; ce pourrait être simplement un oubli.

20

Héritage

Je dirais que le problème est probablement enraciné dans l'héritage. En C:

  • le débordement signé est un comportement indéfini (les compilateurs prennent en charge les indicateurs pour le boucler),
  • le débordement non signé est un comportement défini (il se termine).

Cela a été fait pour obtenir les meilleures performances possibles, en suivant le principe que le programmeur sait ce qu'il fait.

Conduit à Statu-Quo

Le fait que C (et par extension C++) ne nécessite pas la détection de débordement à son tour signifie que la vérification de débordement est lente.

Le matériel s'adresse principalement à C/C++ (sérieusement, x86 a une instruction strcmp (aka PCMPISTRI à partir de SSE 4.2)! ), et comme C s'en fiche, les processeurs courants n'offrent pas de moyens efficaces pour détecter les débordements. Dans x86, vous devez vérifier un indicateur par cœur après chaque opération potentiellement débordante; quand ce que vous voulez vraiment, c'est un drapeau "corrompu" sur le résultat (un peu comme NaN se propage). Et les opérations vectorielles peuvent être encore plus problématiques. Certains nouveaux joueurs peuvent apparaître sur le marché avec une gestion efficace des débordements; mais pour l'instant x86 et ARM s'en foutent.

Les optimiseurs de compilateur ne sont pas bons pour optimiser les vérifications de débordement, ni même pour optimiser en présence de débordements. Certains universitaires comme John Regher se plaignent de ce statu quo , mais le fait est que lorsque le simple fait de faire des débordements "échoue" empêche les optimisations avant même que l'assembly ne frappe, le CPU peut être paralysant. Surtout quand il empêche la vectorisation automatique ...

Avec des effets en cascade

Ainsi, en l'absence de stratégies d'optimisation efficaces et d'un support CPU efficace, la vérification du débordement est coûteuse. Beaucoup plus coûteux que l'emballage.

Ajoutez un comportement ennuyeux, tel que x + y - 1 Peut déborder lorsque x - 1 + y Ne le fait pas, ce qui peut légitimement déranger les utilisateurs, et la vérification de débordement est généralement rejetée en faveur de l'habillage (qui gère cet exemple et beaucoup d'autres gracieusement).

Pourtant, tout espoir n'est pas perdu

Il y a eu un effort dans les compilateurs clang et gcc pour implémenter des "désinfectants": moyens d'instrumenter les binaires pour détecter les cas de comportement indéfini. Lors de l'utilisation de -fsanitize=undefined, Un débordement signé est détecté et abandonne le programme; très utile lors des tests.

Le langage de programmation Rust a la vérification de débordement activée par défaut en mode Debug (il utilise l'arithmétique de wrapping en mode Release pour raisons de performance).

Donc, il y a une inquiétude croissante concernant la vérification des débordements et les dangers de faux résultats non détectés, et j'espère que cela suscitera à son tour spark un intérêt pour la communauté de la recherche, la communauté des compilateurs et la communauté du matériel.

19
Matthieu M.

Les langages qui tentent de détecter les débordements ont historiquement défini la sémantique associée d'une manière qui restreignait sévèrement ce qui aurait autrement été des optimisations utiles. Entre autres choses, bien qu'il soit souvent utile d'effectuer des calculs dans une séquence différente de celle spécifiée dans le code, la plupart des langages qui interceptent les débordements garantissent ce code donné comme:

for (int i=0; i<100; i++)
{
  Operation1();
  x+=i;
  Operation2();
}

si la valeur de départ de x entraîne un débordement lors du 47e passage dans la boucle, Operation1 s'exécutera 47 fois et Operation2 exécutera 46. En l'absence d'une telle garantie, si rien d'autre dans la boucle n'utilise x, et rien utilisera la valeur de x après une exception levée par Operation1 ou Operation2, le code pourrait être remplacé par:

x+=4950;
for (int i=0; i<100; i++)
{
  Operation1();
  Operation2();
}

Malheureusement, effectuer de telles optimisations tout en garantissant une sémantique correcte dans les cas où un débordement se serait produit dans la boucle est difficile - nécessitant essentiellement quelque chose comme:

if (x < INT_MAX-4950)
{
  x+=4950;
  for (int i=0; i<100; i++)
  {
    Operation1();
    Operation2();
  }
}
else
{
  for (int i=0; i<100; i++)
  {
    Operation1();
    x+=i;
    Operation2();
  }
}

Si l'on considère que beaucoup de code du monde réel utilise des boucles plus impliquées, il sera évident qu'il est difficile d'optimiser le code tout en préservant la sémantique de débordement. De plus, en raison de problèmes de mise en cache, il est tout à fait possible que l'augmentation de la taille du code rende le programme global plus lent, même s'il y a moins d'opérations sur le chemin généralement exécuté.

Ce qui serait nécessaire pour rendre la détection de débordement peu coûteuse serait un ensemble défini de sémantique de détection de débordement plus lâche qui permettrait au code de signaler facilement si un calcul a été effectué sans débordements qui auraient pu affecter les résultats (*), mais sans surcharger le compilateur avec des détails au-delà. Si une spécification de langue se concentrait sur la réduction du coût de la détection de débordement au strict minimum nécessaire pour atteindre ce qui précède, elle pourrait être rendue beaucoup moins coûteuse que dans les langues existantes. Je ne connais cependant aucun effort pour faciliter une détection efficace des débordements.

(*) Si une langue promet que tous les débordements seront signalés, alors une expression comme x*y/y ne peut pas être simplifié en x sauf si x*y peut être garanti de ne pas déborder. De même, même si le résultat d'un calcul est ignoré, un langage qui promet de signaler tous les débordements devra de toute façon l'exécuter pour pouvoir effectuer la vérification du débordement. Étant donné que le débordement dans de tels cas ne peut pas entraîner un comportement arithmétiquement incorrect, un programme n'aurait pas besoin d'effectuer de telles vérifications pour garantir qu'aucun débordement n'a entraîné des résultats potentiellement inexacts.

Par ailleurs, les débordements en C sont particulièrement mauvais. Bien que presque toutes les plates-formes matérielles qui prennent en charge C99 utilisent la sémantique enveloppante silencieuse à deux compléments, il est à la mode pour les compilateurs modernes de générer du code qui peut provoquer des effets secondaires arbitraires en cas de débordement. Par exemple, étant donné quelque chose comme:

#include <stdint.h>
uint32_t test(uint16_t x, uint16_t y) { return x*y & 65535u; }
uint32_t test2(uint16_t q, int *p)
{
  uint32_t total=0;
  q|=32768;
  for (int i = 32768; i<=q; i++)
  {
    total+=test(i,65535);
    *p+=1;
  }
  return total;
}

GCC va générer du code pour test2 qui incrémente inconditionnellement (* p) une fois et renvoie 32768 quelle que soit la valeur passée dans q. Par son raisonnement, le calcul de (32769 * 65535) et 65535u provoquerait un débordement et il n'est donc pas nécessaire que le compilateur prenne en compte les cas où (q | 32768) donnerait une valeur supérieure à 32768. Même s'il n'y a pas de raison pour laquelle le calcul de (32769 * 65535) et 65535u devrait se soucier des bits supérieurs du résultat, gcc utilisera le débordement signé comme justification pour ignorer la boucle.

10
supercat

Tous les langages de programmation n'ignorent pas les débordements d'entiers. Certaines langues fournissent des opérations entières sûres pour tous les nombres (la plupart des dialectes LISP, Ruby, Smalltalk, ...) et d'autres via des bibliothèques - par exemple, il existe différentes classes BigInt pour C++.

Le fait qu'un langage protège l'entier du débordement par défaut ou non dépend de son objectif: les langages système tels que C et C++ doivent fournir des abstractions à coût nul et "grand entier" n'en est pas un. Les langages de productivité, tels que Ruby, peuvent fournir et fournissent de gros nombres entiers prêts à l'emploi. Des langages tels que Java et C # qui se situent quelque part entre les deux, à mon humble avis, les entiers sûrs devraient être sortis de la boîte, ce n'est pas le cas.

9

Comme vous l'avez montré, C # aurait été 3 fois plus lent s'il avait activé les vérifications de débordement par défaut (en supposant que votre exemple est une application typique pour ce langage). Je conviens que les performances ne sont pas toujours la caractéristique la plus importante, mais les langages/compilateurs sont généralement comparés sur leurs performances dans les tâches typiques. Cela est dû en partie au fait que la qualité des fonctionnalités du langage est quelque peu subjective, alors qu'un test de performance est objectif.

Si vous deviez introduire un nouveau langage qui est similaire à C # dans la plupart des aspects mais 3 fois plus lent, obtenir une part de marché ne serait pas facile, même si au final la plupart de vos utilisateurs finaux bénéficieraient de contrôles de débordement plus qu'ils ne le feraient de performances supérieures.

7

Au-delà des nombreuses réponses qui justifient le manque de vérification des débordements en fonction des performances, il existe deux types d'arithmétique différents à considérer:

  1. calculs d'indexation (indexation de tableaux et/ou arithmétique de pointeurs)

  2. autre arithmétique

Si le langage utilise une taille entière identique à la taille du pointeur, un programme bien construit ne débordera pas lors des calculs d'indexation car il devra nécessairement manquer de mémoire avant que les calculs d'indexation ne provoquent un débordement.

Ainsi, la vérification des allocations de mémoire est suffisante lorsque vous travaillez avec des expressions arithmétiques de pointeurs et d'indexation impliquant des structures de données allouées. Par exemple, si vous avez un espace d'adressage 32 bits et utilisez des entiers 32 bits et que vous allouez un maximum de 2 Go de segment de mémoire (environ la moitié de l'espace d'adressage), les calculs d'indexation/pointeur (essentiellement) ne déborderont pas.

De plus, vous pourriez être surpris de voir dans quelle mesure l'addition/soustraction/multiplication implique l'indexation du tableau ou le calcul du pointeur, tombant ainsi dans la première catégorie. Le pointeur d'objet, l'accès aux champs et les manipulations de tableaux sont des opérations d'indexation, et de nombreux programmes ne font pas plus de calculs arithmétiques que ceux-ci! Essentiellement, c'est la principale raison pour laquelle les programmes fonctionnent aussi bien qu'ils le font sans vérification de dépassement d'entier.

Tous les calculs sans indexation et sans pointeur doivent être classés comme ceux qui veulent/attendent un débordement (par exemple des calculs de hachage) et ceux qui ne le font pas (par exemple, votre exemple de sommation).

Dans ce dernier cas, les programmeurs utilisent souvent d'autres types de données, tels que double ou certains BigInt. De nombreux calculs nécessitent un type de données decimal plutôt que double, par exemple calculs financiers. S'ils ne le font pas et s'en tiennent aux types entiers, alors ils doivent prendre soin de vérifier le dépassement d'entier - ou bien, oui, le programme peut atteindre une condition d'erreur non détectée comme vous le signalez.

En tant que programmeurs, nous devons être sensibles à nos choix de types de données numériques et à leurs conséquences en termes de possibilités de débordement, sans parler de la précision. En général (et surtout lorsque vous travaillez avec la famille de langages C avec le désir d'utiliser les types entiers rapides), nous devons être sensibles et conscients des différences entre les calculs d'indexation et les autres.

5
Erik Eidt

Dans Swift, tout débordement d'entier est détecté par défaut et arrête instantanément le programme. Dans les cas où vous avez besoin d'un comportement enveloppant, il existe différents opérateurs & +, & - et & * qui y parviennent. Et il existe des fonctions qui effectuent une opération et indiquent s'il y a eu un débordement ou non.

C'est amusant de regarder les débutants essayer d'évaluer la séquence Collatz et faire planter leur code :-)

Désormais, les concepteurs de Swift sont également les concepteurs de LLVM et Clang, donc ils en savent un peu ou deux sur l'optimisation et sont tout à fait capables d'éviter les vérifications de débordement inutiles. Avec toutes les optimisations activées, la vérification de débordement n'ajoute pas grand-chose à la taille du code et au temps d'exécution. Et comme la plupart des débordements conduisent à des résultats absolument incorrects, la taille du code et le temps d'exécution sont bien dépensés.

PS. En C, C++, le dépassement arithmétique d'entier signé Objective-C est un comportement non défini. Cela signifie que tout ce que le compilateur fait en cas de dépassement d'entier signé est correct, par définition. Les moyens typiques de faire face au débordement d'entier signé sont de l'ignorer, en prenant tout résultat que le CPU vous donne, en construisant des hypothèses dans le compilateur qu'un tel débordement ne se produira jamais (et concluez par exemple que n + 1> n est toujours vrai, car le débordement est supposé ne jamais se produire), et une possibilité qui est rarement utilisée est de vérifier et de planter si un débordement se produit, comme Swift le fait.

4
gnasher729

Le langage Rust fournit un compromis intéressant entre la vérification des débordements et non, en ajoutant les vérifications de la version de débogage et en les supprimant dans la version optimisée. Cela vous permet de trouver les bogues pendant les tests, tout en obtenant des performances complètes dans la version finale.

Parce que le bouclage de débordement est parfois un comportement souhaité, il y a aussi versions des opérateurs qui ne vérifie jamais le débordement.

Vous pouvez en savoir plus sur le raisonnement derrière le choix dans le RFC pour le changement. Il y a aussi beaucoup d'informations intéressantes dans cet article de blog , y compris une liste de bogues que cette fonctionnalité a aidé à attraper.

3
Hjulle

En fait, la vraie cause est purement technique/historique: le CPU ignore signe pour la plupart. Il n'y a généralement qu'une seule instruction pour ajouter deux entiers dans les registres, et le CPU se fiche peu que vous interprétiez ces deux entiers comme signés ou non signés. Il en va de même pour la soustraction, et même pour la multiplication. La seule opération arithmétique qui doit être sensible aux signes est la division.

La raison pour laquelle cela fonctionne, est la représentation du complément à 2 des entiers signés qui est utilisée par pratiquement tous les CPU. Par exemple, dans le complément 4 bits 2, l'addition de 5 et -3 ressemble à ceci:

  0101   (5)
  1101   (-3)
(11010)  (carry)
  ----
  0010   (2)

Observez comment le comportement de rebouclage de jeter le bit de report donne le résultat signé correct. De même, les CPU implémentent généralement la soustraction x - y comme x + ~y + 1:

  0101   (5)
  1100   (~3, binary negation!)
(11011)  (carry, we carry in a 1 bit!)
  ----
  0010   (2)

Cela implémente la soustraction en tant qu'addition dans le matériel, modifiant uniquement les entrées de l'unité arithmétique et logique (ALU) de manière triviale. Quoi de plus simple?

Étant donné que la multiplication n'est rien d'autre qu'une séquence d'additions, elle se comporte de manière similaire à Nice. Le résultat de l'utilisation de la représentation du complément à 2 et de l'ignorance de l'exécution des opérations arithmétiques est un circuit simplifié et des jeux d'instructions simplifiés.

De toute évidence, puisque C a été conçu pour fonctionner à proximité du métal, il a adopté exactement le même comportement que le comportement normalisé de l'arithmétique non signée, permettant uniquement à l'arithmétique signée de produire un comportement indéfini. Et ce choix a été appliqué à d'autres langages comme Java et, évidemment, C #.

Certaines réponses ont discuté du coût de la vérification, et vous avez modifié votre réponse pour contester qu'il s'agit d'une justification raisonnable. Je vais essayer de répondre à ces points.

En C et C++ (à titre d'exemples), l'un des principes de conception des langages n'est pas de fournir des fonctionnalités qui n'ont pas été demandées. Ceci est généralement résumé par l'expression "ne payez pas pour ce que vous n'utilisez pas". Si le programmeur veut vérifier le débordement, il peut le demander (et payer la pénalité). Cela rend la langue plus dangereuse à utiliser, mais vous choisissez de travailler avec la langue en sachant cela, vous acceptez donc le risque. Si vous ne voulez pas ce risque, ou si vous écrivez du code où la sécurité est d'une performance primordiale, vous pouvez sélectionner une langue plus appropriée où le compromis performance/risque est différent.

Mais avec les 10 000 000 000 de répétitions, le temps pris par un chèque est toujours inférieur à 1 nanoseconde.

Il y a quelques petites choses qui ne vont pas avec ce raisonnement:

  1. Ceci est spécifique à l'environnement. Il est généralement très peu logique de citer des chiffres spécifiques comme celui-ci, car le code est écrit pour toutes sortes d'environnements qui varient selon des ordres de grandeur en termes de performances. Votre 1 nanoseconde sur (je suppose) une machine de bureau peut sembler incroyablement rapide à quelqu'un qui code pour un environnement embarqué, et insupportablement lente à quelqu'un qui code pour un super cluster informatique.

  2. 1 nanoseconde peut sembler rien pour un segment de code qui s'exécute rarement. D'un autre côté, si c'est dans une boucle interne d'un calcul qui est la fonction principale du code, chaque fraction de temps que vous pouvez raser peut faire une grande différence. Si vous exécutez une simulation sur un cluster, ces fractions de nanosecondes enregistrées dans votre boucle interne peuvent se traduire directement en argent dépensé en matériel et en électricité.

  3. Pour certains algorithmes et contextes, 10 000 000 000 d'itérations pourraient être insignifiantes. Encore une fois, il n'est généralement pas logique de parler de scénarios spécifiques qui ne s'appliquent que dans certains contextes.

Il peut y avoir une situation où cela est important, mais pour la plupart des applications, cela n'a pas d'importance.

Vous avez peut-être raison. Mais encore une fois, il s'agit de savoir quels sont les objectifs d'une langue particulière. De nombreuses langues sont en fait conçues pour répondre aux besoins de "la plupart" ou pour favoriser la sécurité par rapport à d'autres préoccupations. D'autres, comme C et C++, privilégient l'efficacité. Dans ce contexte, faire payer tout le monde une pénalité de performance simplement parce que la plupart des gens ne seront pas dérangés, va à l'encontre de ce que la langue essaie de réaliser.

1
Jon Bentley