615 votes

Ce qui est plus rapide: while(1) ou de tout(2)?

C'était une interview à la question posée par un senior manager.

Ce qui est plus rapide?

while(1) {
    // Some code
}

ou

while(2) {
    //Some code
}

J'ai dit que les deux ont la même vitesse d'exécution, comme l'expression à l'intérieur d' while faut enfin évaluer à l' true ou false. Dans ce cas, à la fois d'évaluer la true et il n'y a pas extra instructions conditionnelles à l'intérieur de l' while condition. Ainsi, les deux auront la même vitesse d'exécution et je préfère tout (1).

Mais l'enquêteur a dit avec confiance: "Vérifiez vos bases. while(1) plus rapide que de l' while(2)." (Il n'était pas test ma confiance)

Est-ce vrai?

Voir aussi: Est "for(;;)" plus rapide que le "while (TRUE)"? Si non, pourquoi les gens utilisent-ils?

717voto

ValekHalfHeart Points 6195

Les deux boucles sont infinies, mais nous pouvons voir que l'on prend plus d'instructions/ressources par itération.

À l'aide de gcc, j'ai compilé les deux programmes suivants à l'assemblée, à divers niveaux d'optimisation:

int main(void)
{
    while(1)
    {
    }

    return 0;
}

int main(void)
{
    while(2)
    {
    }

    return 0;
}

Même sans les optimisations (-O0), l'assembly généré est identique pour les deux programmes. Par conséquent, il n'y a pas de différence de vitesse entre les deux boucles.

Pour référence, voici l'assembly généré (à l'aide d' gcc main.c -S -masm=intel avec une option d'optimisation):

Avec -O0:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    push    rbp
    .seh_pushreg    rbp
    mov rbp, rsp
    .seh_setframe   rbp, 0
    sub rsp, 32
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Avec -O1:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Avec -O2 et -O3 (même résultat):

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

En fait, l'assembly généré par la boucle est identique pour chaque sortie:

 .L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Je ne peux pas lire l'assemblée très bien, mais c'est évidemment une inconditionnelle de la boucle. L' jmp instruction inconditionnellement réinitialise le programme de retour à l' .L2 label sans même comparer une valeur à l'encontre du vrai, et bien sûr immédiatement fait à nouveau jusqu'à ce que le programme est en quelque sorte terminé.

Edit:

Ce qui est assez intéressant, même avec aucun des optimisations, les boucles suivantes, qui sont toutes produites exactement la même sortie (inconditionnel jmp) dans l'assemblée:

while(42)
{
}

while(1==1)
{
}

while(2==2)
{
}

while(4<7)
{
}

while(3==3 && 4==4)
{
}

while(8-9 < 0)
{
}

while(4.3 * 3e4 >= 2 << 6)
{
}

while(-0.1 + 02)
{
}

Et même à mon grand étonnement:

#include<math.h>

while(sqrt(7))
{
}

while(hypot(3,4))
{
}

Les choses deviennent un peu plus intéressantes, avec des fonctions définies par l'utilisateur:

int x(void)
{
    return 1;
}

while(x())
{
}



#include<math.h>

double x(void)
{
    return sqrt(7);
}

while(x())
{
}

En -O0, ces deux exemples d'appels x et d'effectuer une comparaison pour chaque itération.

Premier exemple (retour 1):

.L4:
    call    x
    testl   %eax, %eax
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Deuxième exemple (en rentrant sqrt(7)):

.L4:
    call    x
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jp  .L4
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Toutefois, en -O1 et au-dessus, ils produisent la même assemblée que les exemples précédents (un inconditionnel jmp retour à la précédente étiquette).

TL;DR

Lorsque les différentes boucles sont compilés à l'assemblée, le compilateur évalue les valeurs de constante et n'a pas pris la peine d'effectuer toute comparaison; les deux boucles sont identiques.

Même si cela ne prouve pas que ce comportement est cohérent à travers tous les compilateurs/plates-formes, il s'avère que le compilateur peut optimiser ces boucles identiques, et, par conséquent, devrait. L'un des principaux avantages de l'utilisation d'un langage compilé, c'est le fait que ce genre de chose est censé être à l'extérieur de l'entreprise de programmation de préoccupation.

315voto

Chris Culter Points 2466

Oui, while(1) est beaucoup plus rapide que while(2), pour un homme de le lire! Si je vois, while(1) dans l'inconnu de la base de code, je sais immédiatement ce que l'auteur veut, et mes globes oculaires peuvent continuer à la ligne suivante.

Si je vois, while(2),, je vais probablement arrêter dans mon élan et essayer de comprendre pourquoi l'auteur n'a pas écrit while(1). L'auteur du doigt glisser sur le clavier? Ne les responsables de ce code utiliser while(n) comme un obscur commentant le mécanisme pour faire des boucles de l'air différent? C'est un brut de solution de contournement pour une fausse alerte dans certains brisé outil d'analyse statique? Ou est-ce une idée de ce que je suis en train de lire le code généré? Est-ce un bug résultant d'une mauvaise trouvez et remplacez-les tous les, ou à une mauvaise fusion, ou d'un rayon cosmique? Peut-être que cette ligne de code est censé faire quelque chose de radicalement différent. Peut-être qu'il était censé lire while(w) ou while(x2). Je ferais mieux de trouver l'auteur dans le fichier de l'histoire et de leur envoyer un "WTF" e-mail... et maintenant, j'ai rompu mon mental contexte. L' while(2) peut consommer plusieurs minutes de mon temps, quand while(1) aurait pris une fraction de seconde!

J'exagère, mais seulement un peu. La lisibilité du Code est vraiment important. Et qui vaut la peine de mentionner, dans une interview!

156voto

Keith Thompson Points 85120

Les questions / réponses indiquant le code généré par un compilateur pour une cible particulière, avec un certain ensemble d'options ne répondent pas complètement à la question-à moins que la question a été posée dans ce contexte spécifique ("ce Qui est plus rapide en utilisant gcc 4.7.2 pour x86_64 avec les options par défaut?", pour exemple).

Aussi loin que la définition du langage, dans la machine abstraite while (1) évalue la constante entière 1, et while (2) évalue la constante entière 2; dans les deux cas, le résultat est comparé pour l'égalité à zéro. La langue standard ne dit absolument rien sur la performance relative des deux constructions.

Je peux imaginer qu'une très naïf compilateur peut générer du code machine pour les deux formes, au moins lorsqu'il est compilé sans en demander l'optimisation.

D'autre part, les compilateurs C ne doit absolument évaluer certaines expressions constantes au moment de la compilation, lorsqu'ils apparaissent dans des contextes qui nécessitent une expression constante. Par exemple, ceci:

int n = 4;
switch (n) {
    case 2+2: break;
    case 4:   break;
}

nécessite un diagnostic, un paresseux compilateur n'a pas la possibilité de différer l'évaluation de l' 2+2 jusqu'à l'exécution. Depuis un compilateur doit avoir la capacité d'évaluer des expressions constantes au moment de la compilation, il n'y a aucune bonne raison de ne pas profiter de cette possibilité, même quand il n'est pas nécessaire.

Le C standard (N1570 6.8.5p4) dit que

Une itération de l'instruction d'une déclaration appelée le corps de boucle à exécuté à plusieurs reprises jusqu'à ce que le contrôle de l'expression compare égal à 0.

Donc pertinentes des expressions constantes sont 1 == 0 et 2 == 0, qui s'évaluer à l' int valeur 0. (Ces comparaisons sont implicites dans la sémantique de l' while boucle; ils n'existent pas en tant que réel C expressions.)

Une perverse naïf compilateur peut générer un code différent pour les deux constructions. Par exemple, pour la première elle pourrait générer une inconditionnelle de la boucle infinie (le traitement de l' 0 un cas particulier), et pour le second, il pourrait générer explicite au moment de l'exécution de comparaison équivalent à 2 != 0. Mais je n'ai jamais rencontré un compilateur C qui serait effectivement se comporter de cette façon, et je doute sérieusement qu'un tel compilateur existe.

La plupart des compilateurs (je suis tenté de dire que l'ensemble de la production-de la qualité des compilateurs) ont des options à la demande des optimisations supplémentaires. En vertu de cette option, c'est encore moins probable qu'un compilateur de générer un code différent pour les deux formes.

Si votre compilateur génère un code différent pour les deux constructions, vérifiez d'abord que les différentes séquences de code en fait ont des performances différentes. S'ils le font, essayez de compiler à nouveau avec une option d'optimisation (si disponible). Si elles diffèrent, faire un rapport de bogue pour le fournisseur de compilateur. Ce n'est pas (nécessairement) un bug dans le sens d'une non conformité à la norme C, mais il est presque certainement un problème qui doit être corrigé.

Bottom line: while (1) et while(2) presque certainement avoir les mêmes performances. Ils ont exactement la même sémantique, et il n'y a aucune bonne raison pour n'importe quel compilateur de ne pas générer un code identique.

Et si il est parfaitement légal pour un compilateur de générer plus rapidement le code pour while(1) que while(2), il est tout aussi légal pour un compilateur de générer plus rapidement le code pour while(1) que pour une autre occurrence de while(1) dans le même programme.

(Il y a une autre question implicite dans l'un vous demande: Comment faites-vous face avec un enquêteur qui insiste sur une mauvaise technique. Ce serait sans doute une bonne question pour le lieu de Travail).

150voto

janos Points 22603

Attendre une minute. L'interviewer, avait-il ressembler à ce mec?

enter image description here

Il est assez mauvais que l'enquêteur lui-même a échoué cette interview, que faire si d'autres programmeurs à ce société de "faire passer" ce test?

Pas de. L'évaluation de la consolidés 1 == 0 et 2 == 0 devrait être aussi rapide. Nous pourrions imaginer de mauvais compilateur implémentations où l'on peut être plus rapide que les autres. Mais il n'y a aucune bonne raison pour laquelle on devrait être plus rapide que l'autre.

Même si il y a un peu obscures circonstances où la réclamation serait vrai, les programmeurs ne doit pas être évaluée en se basant sur la connaissance de l'obscur (et dans ce cas, flippant) de trivia. Ne vous inquiétez pas au sujet de cette entrevue, le meilleur choix ici est à pied.

Avertissement: Ce n'est PAS un original de bande dessinée Dilbert. C'est simplement un mashup.

83voto

anatolyg Points 8076

Votre explication est correcte. Cela semble être une question qui teste votre confiance en soi en plus de connaissances techniques.

Par ailleurs, si vous avez répondu

Les deux morceaux de code sont tout aussi rapides, parce que les deux prendre un temps infini pour compléter

l'intervieweur dire

Mais while (1) pouvez faire plus d'itérations par seconde; pouvez-vous expliquer pourquoi? (ce qui est un non-sens; le contrôle de votre confiance encore une fois)

Donc par répondre comme vous l'avez fait, vous avez économisé un peu de temps dont vous auriez autrement des déchets sur l'examen de cette question.


Voici un exemple de code généré par le compilateur sur mon système (MS Visual Studio 2012), avec les optimisations éteint:

yyy:
    xor eax, eax
    cmp eax, 1     (or 2, depending on your code)
    je xxx
    jmp yyy
xxx:
    ...

Avec les optimisations activée:

xxx:
    jmp xxx

Ainsi, le code généré est exactement le même, au moins avec une optimisation du compilateur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X