while (1) Vs. for (;;) y a-t-il une différence de vitesse?
Version Longue...
, Un collègue a affirmé aujourd'hui, après avoir vu mon utilisation de while (1)
dans un script Perl for (;;)
est plus rapide. J'ai soutenu qu'ils devraient être les mêmes en espérant que l'interprète optimiserait les différences. J'ai mis en place un script qui exécuterait 1 000 000 000 pour les itérations de boucle et le même nombre de boucles while et enregistrerait le temps entre. Je ne pouvais trouver aucune différence appréciable. Mon collègue a dit qu'un professeur lui avait dit que le while (1)
faisait une comparaison 1 == 1
et le for (;;)
ne l'était pas. Nous avons répété le même test avec le 100x le nombre d'itérations avec C++ et la différence était négligeable. C'était cependant un exemple graphique de combien le code compilé peut être plus rapide qu'un langage de script.
Version courte...
Y a-t-il une raison de préférer un while (1)
à un for (;;)
si vous avez besoin d'une boucle infinie pour sortir?
Remarque:, Si ce n'est pas clair à partir de la question. Ce fut purement une discussion académique amusant entre un couple des amis. Je suis conscient que ce n'est pas un concept super important que tous les programmeurs devraient agoniser. Merci pour toutes les bonnes réponses que j'AI (et je suis sûr que d'autres) ont appris quelques choses de cette discussion.
Mise à jour: le collègue susmentionné a pesé avec une réponse ci-dessous.
Cité ici au cas où il serait enterré.
Il provient d'un programmeur d'assemblage AMD. Il a déclaré que c programmeurs (le poeple) ne réalisent pas que leur code a inefficacité. Il a dit aujourd'hui cependant, les compilateurs gcc sont très bons, et mettent des gens comme lui de l'entreprise. Il a dit par exemple, et m'a parlé de la
while 1
vsfor(;;)
. Je l'utilise maintenant par habitude mais gcc et surtout les interprètes fera la même opération (un saut de processeur) pour ces deux jours, depuis qu'ils sont optimisés.
20 réponses
En perl, ils aboutissent aux mêmes opcodes:
$ perl -MO=Concise -e 'for(;;) { print "foo\n" }'
a <@> leave[1 ref] vKP/REFC ->(end)
1 <0> enter ->2
2 <;> nextstate(main 2 -e:1) v ->3
9 <2> leaveloop vK/2 ->a
3 <{> enterloop(next->8 last->9 redo->4) v ->4
- <@> lineseq vK ->9
4 <;> nextstate(main 1 -e:1) v ->5
7 <@> print vK ->8
5 <0> pushmark s ->6
6 <$> const[PV "foo\n"] s ->7
8 <0> unstack v ->4
-e syntax OK
$ perl -MO=Concise -e 'while(1) { print "foo\n" }'
a <@> leave[1 ref] vKP/REFC ->(end)
1 <0> enter ->2
2 <;> nextstate(main 2 -e:1) v ->3
9 <2> leaveloop vK/2 ->a
3 <{> enterloop(next->8 last->9 redo->4) v ->4
- <@> lineseq vK ->9
4 <;> nextstate(main 1 -e:1) v ->5
7 <@> print vK ->8
5 <0> pushmark s ->6
6 <$> const[PV "foo\n"] s ->7
8 <0> unstack v ->4
-e syntax OK
De même dans GCC:
#include <stdio.h>
void t_while() {
while(1)
printf("foo\n");
}
void t_for() {
for(;;)
printf("foo\n");
}
.file "test.c"
.section .rodata
.LC0:
.string "foo"
.text
.globl t_while
.type t_while, @function
t_while:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
.L2:
movl $.LC0, %edi
call puts
jmp .L2
.LFE2:
.size t_while, .-t_while
.globl t_for
.type t_for, @function
t_for:
.LFB3:
pushq %rbp
.LCFI2:
movq %rsp, %rbp
.LCFI3:
.L5:
movl $.LC0, %edi
call puts
jmp .L5
.LFE3:
.size t_for, .-t_for
.section .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.string "zR"
.uleb128 0x1
.sleb128 -8
.byte 0x10
.uleb128 0x1
.byte 0x3
.byte 0xc
.uleb128 0x7
.uleb128 0x8
.byte 0x90
.uleb128 0x1
.align 8
.LECIE1:
.LSFDE1:
.long .LEFDE1-.LASFDE1
.LASFDE1:
.long .LASFDE1-.Lframe1
.long .LFB2
.long .LFE2-.LFB2
.uleb128 0x0
.byte 0x4
.long .LCFI0-.LFB2
.byte 0xe
.uleb128 0x10
.byte 0x86
.uleb128 0x2
.byte 0x4
.long .LCFI1-.LCFI0
.byte 0xd
.uleb128 0x6
.align 8
.LEFDE1:
.LSFDE3:
.long .LEFDE3-.LASFDE3
.LASFDE3:
.long .LASFDE3-.Lframe1
.long .LFB3
.long .LFE3-.LFB3
.uleb128 0x0
.byte 0x4
.long .LCFI2-.LFB3
.byte 0xe
.uleb128 0x10
.byte 0x86
.uleb128 0x2
.byte 0x4
.long .LCFI3-.LCFI2
.byte 0xd
.uleb128 0x6
.align 8
.LEFDE3:
.ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
.section .note.GNU-stack,"",@progbits
Donc, je suppose que la réponse est, ils sont les mêmes dans de nombreux compilateurs. Bien sûr, pour d'autres compilateurs, ce n'est peut-être pas nécessairement le cas, mais il y a de fortes chances que le code à l'intérieur de la boucle soit quelques milliers de fois plus cher que la boucle elle-même, alors qui s'en soucie?
En utilisant GCC, ils semblent tous les deux compiler dans le même langage d'assemblage:
L2:
jmp L2
Il n'y a pas beaucoup de raisons de préférer l'un à l'autre. Je pense que while(1)
et en particulier while(true)
sont plus lisibles que for(;;)
, mais c'est juste ma préférence.
Il n'y a pas de différence selon la norme. 6.5.3 / 1 A:
L'instruction for
for ( for-init-statement ; conditionopt ; expressionopt ) statement
Est équivalent à
{
for-init-statement
while ( condition ) {
statement
expression ;
}
}
Et 6.5.3/2 a:
L'une ou l'autre des conditions et l'expression peuvent être omises. Une condition manquante rend la clause implicite while équivalente à while (true).
Donc, selon la norme c++, le code:
for (;;);
Est exactement le même que:
{
while (true) {
;
;
}
}
Le compilateur Visual C++ Utilisé pour émettre un avertissement pour
while (1)
(expression constante), mais pas pour
for (;;)
J'ai continué la pratique de préférer for (;;)
pour cette raison, mais je ne sais pas si le compilateur le fait encore ces jours-ci.
for(;;)
est-ce qu'un caractère de moins à taper si vous voulez aller dans cette direction pour optimiser les choses.
Turbo C avec ces anciens compilateurs for(;;)
entraîne un code plus rapide que while(1)
.
Aujourd'hui gcc, les compilateurs Visual C (je pense que presque tous) optimisent bien, et les processeurs avec 4.7 MHz sont rarement utilisés.
À cette époque, un for( i=10; i; i-- )
était plus rapide que for( i=1; i <=10; i++ )
, car comparer i
est 0, entraîne un saut conditionnel CPU-Zéro-drapeau. Et le drapeau Zéro a été modifié avec la dernière opération de décrémentation ( i-- )
, aucune opération cmp supplémentaire n'est nécessaire.
call __printf_chk
decl %ebx %ebx=iterator i
jnz .L2
movl -4(%ebp), %ebx
leave
Et ici avec for(i=1; i<=10; i++)
, avec supplément cmpl:
call __printf_chk
incl %ebx
cmpl $11, %ebx
jne .L2
movl -4(%ebp), %ebx
leave
Pour toutes les personnes qui se disputent, vous ne devriez pas utiliser des boucles indéfinies while, Et suggérer des trucs stupides comme utiliser open goto ( sérieusement, aïe)
while (1) {
last if( condition1 );
code();
more_code();
last if( condition2 );
even_more_code();
}
Ne peut pas vraiment être représenté efficacement d'une autre manière. Non sans créer une variable de sortie et faire de la magie noire pour la synchroniser.
Si vous avez un penchant pour la syntaxe plus goto-esque, utilisez quelque chose de sain d'esprit qui limite la portée.
flow: {
if ( condition ){
redo flow;
}
if ( othercondition ){
redo flow;
}
if ( earlyexit ){
last flow;
}
something(); # doesn't execute when earlyexit is true
}
En fin de compte, la vitesse n'est pas si importante
S'inquiéter de l'efficacité des différentes constructions en boucle en termes de vitesse est une perte de temps massive. Optimisation prématurée à travers et à travers. Je ne peux pas penser à une situation que j'ai jamais vue où le code de profilage a trouvé des goulots d'étranglement dans mon choix de construction en boucle.
Généralement son la comment de la boucle et de la ce de la boucle.
Vous devriez "optimiser" pour la lisibilité et la concision, et écrire ce qui est le mieux pour expliquer le problème au suivant pauvre suceur qui trouve votre code.
Si vous utilisez le truc" Goto LABEL " mentionné par quelqu'un, et que je dois utiliser votre code, soyez prêt à dormir avec un œil ouvert, surtout si vous le faites plus d'une fois, parce que ce genre de choses crée horrible code spaghetti.
Juste parce que vous peut créer du code spaghetti ne signifie pas que vous devraient
De Stroustrup, TC++PL (3ème édition), §6.1.1:
La notation curieuse
for (;;)
est la manière standard de spécifier une boucle infinie; vous pouvez la prononcer "pour toujours". [...]while (true)
est une alternative.
Je préfère for (;;)
.
J'en ai entendu parler une fois.
Il provient d'un programmeur d'assemblage AMD. Il a déclaré que les programmeurs C (Les gens) ne réalisent pas que leur code a des inefficacités. Il a dit aujourd'hui cependant, les compilateurs gcc sont très bons, et mettent des gens comme lui hors d'affaires. Il a dit par exemple, et m'a parlé de la while 1
vs for(;;)
. Je l'utilise maintenant par habitude mais gcc et surtout les interprètes feront la même opération (un saut de processeur) pour ces deux jours, car ils sont optimisés.
Si le compilateur ne fait aucune optimisation, {[1] } serait toujours plus rapide que while(true)
. En effet, while-statement évalue la condition à chaque fois, mais for-statement est un saut inconditionnel. Mais si le compilateur optimise le flux de contrôle, il peut générer des opcodes. Vous pouvez lire le code de démontage très facilement.
P.S. vous pourriez écrire une boucle infinie comme ceci:
#define EVER ;;
//...
for (EVER) {
//...
}
Dans une construction optimisée d'un langage compilé, il ne devrait pas y avoir de différence appréciable entre les deux. Ni ne devrait finir par effectuer des comparaisons à l'exécution, ils exécuteront simplement le code de boucle jusqu'à ce que vous quittiez manuellement la boucle (par exemple avec un break
).
Je suis surpris que personne n'correctement testé for (;;)
contre while (1)
en perl!
Parce que perl est un langage interprété, le temps d'exécution d'un script perl ne consiste pas seulement en la phase d'exécution (qui dans ce cas est la même) mais aussi en la phase d'interprétation avant l'exécution. Ces deux phases doivent être prises en compte lors de la comparaison de vitesse.
Heureusement, perl a un module Benchmark pratique que nous pouvons utiliser pour implémenter un benchmark tel que suit:
#!/usr/bin/perl -w
use Benchmark qw( cmpthese );
sub t_for { eval 'die; for (;;) { }'; }
sub t_for2 { eval 'die; for (;;) { }'; }
sub t_while { eval 'die; while (1) { }'; }
cmpthese(-60, { for => \&t_for, for2 => \&t_for2, while => \&t_while });
Notez que je teste deux versions différentes de la boucle infinite for: une qui est plus courte que la boucle while et une autre qui a un espace supplémentaire pour la rendre de la même longueur que la boucle while.
Sur Ubuntu 11.04 x86_64 avec perl 5.10.1 j'obtiens les résultats suivants:
Rate for for2 while for 100588/s -- -0% -2% for2 100937/s 0% -- -1% while 102147/s 2% 1% --
La boucle while est clairement le gagnant sur cette plate-forme.
Sur FreeBSD 8.2 x86_64 avec perl 5.14.1:
Rate for for2 while for 53453/s -- -0% -2% for2 53552/s 0% -- -2% while 54564/s 2% 2% --
Alors que loop est le gagnant ici aussi.
Sur FreeBSD 8.2 i386 avec perl 5.14.1:
Rate while for for2 while 24311/s -- -1% -1% for 24481/s 1% -- -1% for2 24637/s 1% 1% --
Étonnamment, la boucle for avec un espace supplémentaire est le choix le plus rapide ici!
Ma conclusion est que la boucle while devrait être utilisée sur la plate-forme x86_64 si le programmeur optimise la vitesse. Évidemment une boucle for doit être utilisé lors de l'optimisation de l'espace. Mes résultats sont malheureusement peu concluants concernant d'autres plates-formes.
En théorie, un compilateurcomplètement naïf pourrait stocker le littéral ' 1 ' dans le binaire (perdre de l'espace) et vérifier si 1 = = 0 chaque itération (perdre du temps et plus d'espace).
En réalité, même avec des optimisations "non", les compilateurs réduiront toujours les deux à la même chose. Ils peuvent également émettre des avertissements car cela pourrait indiquer une erreur logique. Par exemple, l'argument de while
pourrait être défini ailleurs et vous ne réalisez pas qu'il est constant.
Je suis surpris que personne n'ait offert la forme la plus directe, correspondant à l'assemblage souhaité:
forever:
do stuff;
goto forever;
while(1)
est un idiome pour for(;;)
, qui est reconnu par la plupart des compilateurs.
J'étais heureux de voir que perl reconnaît until(0)
, aussi.
Pour résumer le débat for (;;)
vs while (1)
Il est évident que le premier était plus rapide à l'époque des anciens compilateurs non optimisés, c'est pourquoi vous avez tendance à le voir dans les anciennes bases de code telles que Lions Unix source code commentary, cependant à l'ère des compilateurs d'optimisation badass ces gains sont optimisés en couplant cela avec le fait que ce dernier est plus facile à comprendre que le premier, je crois que ce serait plus préférable.
Vient de tomber sur ce fil (bien que quelques années de retard).
Je pense avoir trouvé la raison réelle pour laquelle "for (;;)" Est meilleur que " while (1)".
Selon la "norme de codage barr 2018"
Kernighan & Ritchie long ago recommended for (;;) , which has the additional benefit
of insuring against the visually-confusing defect of a while (l); referencing a variable ‘l’.
Fondamentalement, ce n'est pas un problème de vitesse mais un problème de lisibilité. Selon la police / l'impression du code, le numéro un (1) dans un certain temps peut ressembler à une lettre minuscule L.
C'est-à-dire 1 vs L. (Dans certaines polices, celles-ci semblent identiques).
Alors que (1) peut ressembler à certains alors que la boucle dépend de la lettre variable L.
While (true)peut également fonctionner mais dans certains cas c plus anciens et c embarqués, true / false ne sont pas encore définis à moins que stdbool.h est inclus.
Je pense que les deux sont les mêmes en termes de performance. Mais je préférerais while (1) pour la lisibilité mais je me demande pourquoi vous avez besoin d'une boucle infinie.
Ils sont les mêmes. Il y a des questions beaucoup plus importantes à réfléchir.