Je viens d'utiliser ~ 1 milliard pour le compte d'un z-index
en CSS, et réfléchissait aux comparaisons qui devaient continuer. Existe-t-il une différence de performances au niveau ALU dans les comparaisons entre les très grands nombres et les très petits?
Par exemple, l'un de ces deux extraits serait-il plus cher que l'autre?
snippet 1
for (int i = 0; i < 10000000; i++){
if (i < 10000000000000) {
//do nothing
}
}
snippet 2
for (int i = 0; i < 10000000; i++){
if (i < 1000) {
//do nothing
}
}
Chaque processeur sur lequel j'ai travaillé fait une comparaison en soustrayant l'un des opérandes de l'autre, en rejetant le résultat et en laissant les drapeaux du processeur (zéro, négatif, etc.) seuls. Étant donné que la soustraction est effectuée en une seule opération, le contenu des opérandes n'a pas d'importance.
La meilleure façon de répondre à la question est de compiler votre code dans Assembly et de consulter la documentation du processeur cible pour les instructions générées. Pour les processeurs Intel actuels, ce serait le Intel 64 et IA-32 Architectures Software Developer’s Manual .
La description de l'instruction CMP
("comparer") se trouve dans le volume 2A, page 3-126 ou page 618 du PDF, et décrit son fonctionnement comme suit:
temp ← SRC1 − SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)
Cela signifie que le deuxième opérande est étendu par signe si nécessaire, soustrait du premier opérande et le résultat placé dans une zone temporaire dans le processeur. Les indicateurs d'état sont ensuite définis de la même manière que pour l'instruction SUB
("soustraire") (page 1492 du PDF).
Il n'y a aucune mention dans la documentation CMP
ou SUB
que les valeurs des opérandes ont une incidence sur la latence, donc toute valeur que vous utilisez est sûre.
Existe-t-il une différence de performances au niveau ALU dans les comparaisons entre les très grands nombres et les très petits?
C'est très improbable, sauf si le passage d'un petit nombre à un grand nombre modifie votre type numérique, par exemple d'un int
à un long
. Même alors, la différence pourrait ne pas être significative. Vous êtes plus susceptible de voir une différence si votre langage de programmation passe silencieusement à arithmétique de précision arbitraire sous les couvertures.
Néanmoins, votre compilateur particulier peut effectuer des optimisations intelligentes que vous ne connaissez pas. La façon dont vous le découvrez est de mesurer. Exécutez un profileur sur votre code; voir quelles comparaisons prennent le plus de temps. Ou tout simplement démarrer et arrêter une minuterie.
De nombreux processeurs ont de "petites" instructions qui peuvent effectuer des opérations arithmétiques, y compris des comparaisons, sur certains opérandes immédiatement spécifiés. Les opérandes autres que ces valeurs spéciales doivent utiliser un format d'instruction plus grand ou, dans certains cas, doivent utiliser une instruction "charger la valeur à partir de la mémoire". Dans le jeu d'instructions ARM Cortex-M3, par exemple, il existe au moins cinq façons de comparer une valeur à une constante:
cmp r0,#1 ; One-Word instruction, limited to values 0-255
cmp r0,#1000 ; Two-Word instruction, limited to values 0-255 times a power of 2
cmn r0,#1000 ; Equivalent to comparing value with -1000
; Two-Word instruction, limited to values 0-255 times a power of 2
mov r1,#30000 ; Two words; can handle any value 0-65535
cmp r0,r1 ; Could use cmn to compare to values -1 to -65535
ldr r1,[constant1000000] ; One or two words, based upon how nearby the constant is
cmp r0,r1
...
constant1000000:
dd 1000000
La première forme est la plus petite; les deuxième et troisième formulaires peuvent ou non s'exécuter aussi rapidement, selon la vitesse de la mémoire à partir de laquelle le code est extrait. Le quatrième formulaire sera certainement plus lent que les trois premiers, et le cinquième sera encore plus lent, mais ce dernier peut être utilisé avec n'importe quelle valeur de 32 bits.
Sur les processeurs x86 plus anciens, les instructions de comparaison de forme courte s'exécuteraient plus rapidement que celles de forme longue, mais de nombreux processeurs plus récents convertiront les formes longue et courte en la même représentation lors de leur première extraction et stockeront cette représentation uniforme dans le cache. Ainsi, alors que les contrôleurs intégrés (comme ceux que l'on trouve sur de nombreuses plates-formes mobiles) auront une différence de vitesse, de nombreux ordinateurs x86 ne le feront pas.
Notez également que dans de nombreux cas où une constante est fortement utilisée dans une boucle, un compilateur n'aura besoin de charger la constante dans un registre qu'une seule fois - avant le début de la boucle - ce qui rend les distinctions de synchronisation sans objet. D'un autre côté, il y a des situations, même en petites boucles, où cela ne se produira pas toujours; si une boucle est petite mais lourdement exécutée, il peut parfois y avoir une performance majeure entre les comparaisons impliquant des valeurs immédiates courtes et celles impliquant des valeurs plus longues.
La réponse courte à cette question est, non , il n'y a pas de différence de temps pour comparer deux nombres en fonction de l'ampleur de ces nombres en supposant qu'ils sont stockés dans le même type de données (par exemple, les deux entiers 32 bits ou les deux longs 64 bits.)
De plus, jusqu'à la taille Word de ALU , il est incroyablement improbable que la comparaison de deux nombres entiers prenne plus d'un cycle d'horloge, car c'est une opération triviale équivalente à une soustraction. Je pense que chaque architecture que j'ai jamais eue avait une comparaison d'entiers à cycle unique.
Les seuls cas auxquels je peux penser que j'ai rencontrés où une comparaison de deux nombres n'était pas une opération à cycle unique sont les suivants:
@ réponse de RobertHarvey est bon; considérez cette réponse comme un complément à la sienne.
Vous devriez également considérer Branch Prediction :
Dans l'architecture informatique, un prédicteur de branche est un circuit numérique qui essaie de deviner dans quelle direction une branche (par exemple une structure si-alors-sinon) ira avant que cela ne soit sûr. Le prédicteur de branche a pour but d'améliorer le flux dans le pipeline d'instructions. Les prédicteurs de branche jouent un rôle essentiel dans la réalisation de performances efficaces élevées dans de nombreuses architectures de microprocesseurs en pipeline modernes telles que x86.
Fondamentalement, dans votre exemple, si l'instruction if
à l'intérieur de la boucle renvoie toujours la même réponse, alors le système peut l'optimiser en devinant correctement de quelle manière il se branchera. Dans votre exemple, étant donné que l'instruction if
dans le premier cas renvoie toujours le même résultat, elle s'exécutera légèrement plus rapidement que dans le second cas.
Cela dépend de l'implémentation, mais ce serait très, très peu probable.
J'avoue que je n'ai pas lu les détails d'implémentation des différents moteurs de navigation, et CSS ne spécifie aucun type particulier de stockage pour les nombres. Mais je crois qu'il est sûr de supposer que tous les principaux navigateurs utilisent des nombres à virgule flottante double précision 64 bits ("doubles", pour emprunter un terme en C/C++) pour gérer la plupart de leurs besoins numériques en CSS , car c'est ce que JavaScript utilise pour les nombres, et donc l'utilisation du même type facilite l'intégration.
Du point de vue de l'ordinateur, tous les doubles transportent la même quantité de données: 64 bits, que la valeur soit 1 ou -3,14 ou 1000000 ou 1e1. Le temps nécessaire pour effectuer une opération sur ces nombres ne dépend pas de la valeur réelle de ces nombres, car cela fonctionne toujours sur la même quantité de données. Il y a un compromis à faire les choses de cette façon, en ce sens que les doubles ne peuvent pas représenter avec précision tous les nombres (ou même tous les nombres dans leur plage), mais ils peuvent se rapprocher suffisamment pour la plupart des questions, et le genre de choses que CSS ne fait pas numériquement - assez exigeant pour avoir besoin de plus de précision que cela. Combinez cela avec les avantages de la compatibilité directe avec JavaScript, et vous avez un argument assez solide pour les doubles.
Il n'est pas impossible que quelqu'un implémente CSS en utilisant un codage de longueur variable pour les nombres. Si quelqu'un a utilisé un encodage de longueur variable, alors comparer avec de petits nombres serait moins cher que comparer avec de grands nombres, parce que les grands nombres ont plus de données à croquer. Ces types d'encodages peuvent être plus précis que binaires, mais ils sont également beaucoup plus lents, et pour CSS en particulier, les gains de précision ne sont probablement pas suffisants pour valoir le coup des performances. Je serais très surpris d'apprendre que n'importe quel navigateur faisait les choses de cette façon.
Maintenant, en théorie, il y a une exception possible à tout ce que j'ai dit ci-dessus: comparer avec zéro est souvent plus rapide que comparer avec d'autres nombres. Ce n'est pas parce que zéro est court (si c'était la raison, alors 1 devrait être tout aussi rapide, mais ce n'est pas le cas). C'est parce que zéro vous permet de tricher. C'est le seul nombre où tous les bits sont désactivés, donc si vous savez que l'une des valeurs est zéro, vous n'avez même pas besoin de regarder l'autre valeur comme un nombre: si l'un des bits est alors il n'est pas égal à zéro, puis il suffit de regarder un bit pour voir s'il est supérieur ou inférieur à zéro.
Si ce code était interprété à chaque exécution, il y aurait une différence car il faut plus de temps pour tokeniser et interpréter 10000000000000
par rapport à 1000
. Cependant, c'est la première optimisation évidente des interprètes dans ce cas: jetez une fois et interprétez les jetons.