Une comparaison 1 <10 est-elle moins chère que 1 <1000000?


65

Je viens d'utiliser ~ 1 milliard comme compte pour un z-indexCSS, et je réfléchissais aux comparaisons qui doivent se faire. Existe-t-il une différence de performance au niveau des UAL 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 coûteux 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
    }
}


12
OP ne demande pas combien de temps prendra la ramification. Il est clair que cet exemple a pour but de garantir que cela prend exactement le même temps dans les deux extraits. La question est de savoir si l' CMPinstruction individuelle de la machine sera plus lente si elle iest plus grande.
Kilian Foth

18
Comme cela est fait en CSS, la conversion d'une chaîne en un entier dominera probablement l'opération de comparaison elle-même en termes de temps d'exécution.

58
Si vous aviez besoin d'utiliser 1000000000 comme z-index dans un fichier CSS, vous avez commis une erreur.
Bergi

6
Pour CSS, la surcharge de la conversion de texte en entier dépendra du nombre de chiffres convertis (un nombre à 6 chiffres tel que 1000000 peut être environ 6 fois plus cher qu'un nombre à 1 chiffre tel que 1); et ces frais généraux peuvent être des ordres de grandeur plus grands que ceux des comparaisons d'entiers.
Brendan

Réponses:


82

Chaque processeur sur lequel j'ai travaillé effectue la comparaison en soustrayant l'un des opérandes de l'autre, en ignorant le résultat et en laissant les indicateurs du processeur (zéro, négatif, etc.). Comme la soustraction est effectuée en une seule opération, le contenu des opérandes importe peu.

La meilleure façon de répondre à la question est de compiler votre code en assembleur et de consulter la documentation du processeur cible pour connaître les instructions générées. Pour les processeurs Intel actuels, il s'agirait du Manuel du développeur de logiciels pour architectures Intel 64 et IA-32 .

La description de l' CMPinstruction ("comparer") se trouve dans le volume 2A, page 3-126 ou page 618 du document 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 si nécessaire au signe, soustrait du premier opérande et que le résultat est placé dans une zone temporaire du processeur. Ensuite, les indicateurs d'état sont définis de la même manière que pour l' SUBinstruction ("soustraire") (page 1492 du document PDF).

La documentation CMPou la SUBdocumentation ne mentionnent pas que les valeurs des opérandes ont une incidence sur le temps de latence. Par conséquent, toute valeur que vous utilisez est sûre.


5
Que faire si le nombre devient trop grand pour l'arithmétique 32 bits? Ne serait-il pas alors divisé en calcul plus lent?
Falco

3
@Falco Pas sur un processeur avec une ALU 64 bits (ce qui est quasiment la totalité d'entre eux sauf dans l'espace embarqué de nos jours.)
reirab

8
@ Falco: Oui, mais comme la question concerne les performances de l'ALU, il en découle que les valeurs correspondent à la taille de mot de la CPU ou aux capacités de toute instruction SIMD qu'il pourrait avoir. Opérer sur de plus grands nombres que cela devrait être implémenté avec plusieurs instructions en dehors de la CPU. C'était très courant il y a 30 ans, lorsque vous ne disposiez que de registres à 8 ou 16 bits.
Blrfl

6
@ Falco Comment cela nécessiterait-il un débogage? Ce n'est pas un bug il est juste un peu plus lent de faire des opérations 64 bits sur un processeur qui ne prend pas nativement en charge les opérations 64 bits. Suggérer qu'il ne faut jamais utiliser un nombre supérieur à 2 ^ 31-1 semble un peu ridicule.
Reirab

2
@Falco Cela dit, les moteurs de rendu des navigateurs utilisent-ils même des entiers pour représenter les indices z? La plupart des moteurs de rendu que je connais utilisent des flottants à simple précision pour tout (jusqu'à l'étape finale de la rastérisation), mais je n'ai pas vraiment étudié les moteurs de rendu pour les navigateurs.
Reirab

25

Existe-t-il une différence de performance au niveau des UAL dans les comparaisons entre les très grands nombres et les très petits?

C'est très improbable, à moins de passer d'un nombre petit à un nombre élevé, change votre type de chiffre, par exemple d' intun long. Même dans ce cas, la différence pourrait ne pas être significative. Il est plus probable que vous constatiez une différence si votre langage de programmation bascule en silence sur l'arithmétique en précision arbitraire sous les couvertures.

Néanmoins, votre compilateur peut effectuer des optimisations intelligentes dont vous n'êtes pas au courant. La façon dont vous le découvrez est de mesurer. Exécutez un profileur sur votre code; voir quelles comparaisons prennent le plus longtemps. Ou tout simplement démarrer et arrêter une minuterie.


Il convient de mentionner que les numéros proposés dans la question sont de types numériques différents dans un type entier de type 32 bits ...
Falco

19

De nombreux processeurs ont de "petites" instructions capables d'effectuer des opérations arithmétiques, notamment des comparaisons, sur certains opérandes spécifiés immédiatement. Les opérandes autres que ces valeurs spéciales doivent soit utiliser un format d'instruction plus grand, soit, dans certains cas, utiliser une instruction "charger la valeur 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 formes peuvent ou non être exécutées aussi rapidement, en fonction de la vitesse de la mémoire à partir de laquelle le code est extrait. La quatrième forme sera presque certainement plus lente que les trois premières, et la cinquième encore plus lente, mais cette dernière peut être utilisée avec n'importe quelle valeur 32 bits.

Sur les processeurs x86 plus anciens, les instructions de comparaison de forme abrégée s'exécutent plus rapidement que celles de forme longue, mais de nombreux processeurs plus récents convertissent les formulaires long et court en la même représentation lors de leur première extraction et stockent 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 à charger la constante dans un registre qu'une seule fois - avant le début de la boucle - rendant les distinctions temporelles fictives. D'un autre côté, il y a des situations, même dans de petites boucles, où cela ne se produit pas toujours. Si une boucle est petite mais fortement 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.


Sur MIPS, vous ne pouvez avoir qu’immédiatement en 16 bits, donc la comparaison avec 1 sera certainement plus courte et (probablement) plus rapide que 1000000. Peut-être la même chose pour Sparc et PowerPC. Et je pense avoir lu dans certaines sources qu'Intel optimise également les opérations sur les petits immédiats dans plusieurs cas, mais je ne suis pas sûr de pouvoir le comparer ou non
phuclv

@ LưuVĩnhPhúc: Un registre peut être chargé avant la boucle. À ce stade, la comparaison réelle correspondra au même nombre d'instructions dans les deux cas.
cHao

Comme la boucle n'était qu'un exemple de l'op et que la question était par exemple un z-index, si vous avez 1000 objets, chacun avec son propre z-index et que vous les définissez à 100000000 ... 1000000999 ou à 10 000 ... 10999 et que vous passez dessus pour effectuer un tri avant le rendu, il existe de nombreuses comparaisons et de nombreuses instructions de chargement. Là ça pourrait faire la différence!
Falco

@ Falco: Dans ce cas, l'immédiat ne prendrait même pas en compte; le chargement et la comparaison avec un registre semblent plutôt inévitables.
cHao

@ cHao: Si l'on compare les indices Z les uns aux autres, ils seraient dans des registres. Si l'on manipule différemment certaines plages d'indices, des comparaisons immédiates peuvent être nécessaires. Normalement, les constantes sont chargées avant le début d’une boucle, mais si, par exemple, une boucle nécessitait de lire des paires de valeurs dans la mémoire et de comparer la première valeur de chaque paire avec cinq constantes différentes (non uniformément espacées) dans la plage 100000 à 100499, et l'autre valeur avec cinq autres constantes de ce type, il peut être beaucoup plus rapide de soustraire 100250 (conservé dans un registre), puis de le comparer aux valeurs de 250 à 250 ...
supercat

5

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 leur magnitude, en supposant qu'ils sont stockés dans le même type de données (par exemple, les deux inits 32 bits ou les bits longs de 64 bits).

De plus, jusqu'à la taille de mot de l' ALU , il est extrêmement improbable que la comparaison de deux nombres entiers prenne plus d'un cycle d'horloge, car il s'agit d'une opération triviale équivalente à une soustraction. Je pense que chaque architecture à laquelle j'ai eu affaire avait une comparaison d’entiers sur un seul cycle.

Les seuls cas auxquels je peux penser que j'ai rencontrés dans lesquels la comparaison de deux nombres n'était pas une opération à cycle unique sont les suivants:

  • Instructions dans lesquelles il y a effectivement une latence de la mémoire dans les opérandes d'extraction, mais cela n'a rien à voir avec le fonctionnement de la comparaison (et n'est généralement pas possible sur les architectures RISC, bien que cela soit généralement possible avec les conceptions CISC, telles que x86 / x64.)
  • Les comparaisons en virgule flottante peuvent être multi-cycles, en fonction de l'architecture.
  • Les nombres en question ne correspondent pas à la taille de mot de l'ALU et, par conséquent, la comparaison doit être divisée en plusieurs instructions.

4

@ La réponse de RobertHarvey est bonne; considérez cette réponse comme un complément à la sienne.


Vous devriez également envisager la prévision de branche :

Dans l'architecture informatique, un prédicteur de branche est un circuit numérique qui essaie de deviner le sens d'une branche (par exemple, une structure if-then-else) avant que cela ne soit connu avec certitude. L'objectif du prédicteur de branche est d'améliorer le flux dans le pipeline d'instructions. Les prédicteurs de branche jouent un rôle essentiel dans l'obtention de performances efficaces élevées dans de nombreuses architectures de microprocesseurs pipelined modernes telles que x86.

Fondamentalement, dans votre exemple, si l’ ifinstruction dans la boucle renvoie toujours la même réponse, le système peut l’optimiser en devinant correctement le sens de la branche. Dans votre exemple, étant donné que l' ifinstruction dans le premier cas renvoie toujours le même résultat, son exécution sera légèrement plus rapide que dans le deuxième cas.

Excellente question de débordement de pile sur le sujet


La prévision de branche affecte le temps de branchement, mais pas le temps de comparaison lui-même.
Reirab

3

Cela dépend de la mise en œuvre, mais ce serait très, très improbable .

J'avoue que je n'ai pas lu les détails de la mise en œuvre des différents moteurs de navigateur et que CSS ne spécifie aucun type de stockage particulier pour les numéros. Mais je pense qu’il est raisonnable de supposer que tous les principaux navigateurs utilisent des nombres à virgule flottante double précision 64 bits ("doubles", pour emprunter un terme de C / C ++) afin de gérer la plupart de leurs besoins numériques en CSS. , car c’est ce que JavaScript utilise pour les nombres, et l’utilisation du même type facilite donc 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 1e100 . La durée nécessaire pour effectuer une opération sur ces chiffres ne dépend pas de la valeur réelle de ces chiffres, car elle fonctionne toujours sur le même nombre de données. Il y a un compromis en faisant les choses de cette façon, en ce que les doubles ne peuvent pas représenter avec précision tous les nombres (ou même tous les nombres dans leur intervalle), mais ils peuvent être assez proches pour la plupart des choses, 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 obtenez un cas assez solide pour les doublons.

Il n'est pas impossible que quelqu'un implémente CSS à l'aide d'un codage à longueur variable pour les nombres. Si quelqu'un utilisait un codage de longueur variable, alors comparer avec de petits nombres coûterait moins cher que comparer avec de grands nombres, car les grands nombres ont plus de données à traiter . Ces types d’encodage peuvent être plus précis que les fichiers 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 les performances. Je serais très surpris d'apprendre que n'importe quel navigateur fait les choses de cette façon.

Maintenant, en théorie, il y a une exception possible à tout ce que j'ai dit plus haut: comparer avec zéro est souvent plus rapide que de comparer avec d'autres chiffres . Ce n'est pas parce que zéro est court (si c'était la raison, alors 1 devrait être 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. Par conséquent, si vous savez qu'une des valeurs est zéro, vous n'avez même pas à regarder l'autre valeur sous forme de nombre: si l'un des bits est actif, 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.


0

Si ce code était interprété à chaque fois qu'il était exécuté, il y aurait une différence, car la mise en place de marques et d'interprétations prend plus de temps 10000000000000par rapport à 1000. Cependant, il s’agit de la première optimisation évidente des interprètes dans ce cas: tokenise une fois et interprète les jetons.

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.