web-dev-qa-db-fra.com

tandis que (1) Vs. pour (;;) 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 que 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 d'itérations de boucle et le même nombre de boucles while et enregistrer le temps entre les deux. Je n'ai trouvé aucune différence appréciable. Mon collègue a dit qu'un professeur lui avait dit que la while (1) faisait une comparaison 1 == 1 Et que la for (;;) ne l'était pas. Nous avons répété le même test avec 100 fois le nombre d'itérations avec C++ et la différence était négligeable. Il s'agissait cependant d'un exemple graphique de la rapidité avec laquelle un code compilé peut être comparé à un langage de script.

Version courte...

Y a-t-il une raison de préférer une while (1) à une for (;;) si vous avez besoin d'une boucle infinie pour sortir?

Remarque: Si cela ne ressort pas clairement de la question. C'était purement une discussion académique amusante entre deux amis. Je suis conscient que ce n'est pas un concept super important sur lequel tous les programmeurs devraient agoniser. Merci pour toutes les excellentes réponses que j'ai (et je suis sûr que d'autres) ont appris 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 provenait d'un programmeur d'AMD Assembly. Il a déclaré que les programmeurs C (le poeple) ne réalisent pas que leur code a des inefficacités. Il a dit aujourd'hui cependant que les compilateurs gcc sont très bons et mettent des gens comme lui à la faillite. Il a dit par exemple, et m'a parlé de 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.

151
Copas

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 certains autres compilateurs, cela ne peut pas nécessairement être le cas, mais les chances sont que le code à l'intérieur de la boucle va être quelques milliers de fois plus cher que la boucle elle-même, alors qui s'en soucie?

215
bdonlan

En utilisant GCC, ils semblent tous deux compiler dans le même langage d'assemblage:

L2:
        jmp     L2
55
Martin Cote

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.

53
Bill the Lizard

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 les deux conditions et l'expression peuvent être omises. Une condition manquante rend la clause while implicite équivalente à while (true).

Ainsi, selon la norme C++, le code:

for (;;);

est exactement le même que:

{
  while (true) {
    ;
    ;
  }
}
31
Richard Corden

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 de nos jours.

28
sean e

for(;;) est un caractère de moins à taper si vous voulez aller dans cette direction pour optimiser les choses.

26
Chris Bartow

Turbo C avec ces anciens compilateurs for(;;) donne un code plus rapide que while(1).

Aujourd'hui, les compilateurs gcc, Visual C (je pense que presque tous) optimisent bien, et les processeurs à 4,7 MHz sont rarement utilisés.

À cette époque, une for( i=10; i; i-- ) était plus rapide que for( i=1; i <=10; i++ ), car comparer i est égal à 0, entraîne un saut conditionnel CPU-Zero-Flag. Et le Zero-Flag 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 cmpl supplémentaire:

    call    __printf_chk
    incl    %ebx
    cmpl    $11, %ebx
    jne     .L2
    movl    -4(%ebp), %ebx
    leave
20
Lutz L.

Pour toutes les personnes qui soutiennent que vous ne devriez pas utiliser de boucles while indéfinies, et suggérer des trucs idiots 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 garder synchronisée.

Si vous avez un penchant pour la syntaxe plus goto-esque, utilisez quelque chose de sain 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é de différentes constructions en boucle en termes de vitesse est une énorme perte de temps. Optimisation prématurée de bout en bout. Je ne peux penser à aucune 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, c'est le comment de la boucle et le quoi de la boucle.

Vous devez "optimiser" la lisibilité et la concision, et écrire tout ce qui est le mieux pour expliquer le problème au prochain mauvais surgeon qui trouve votre code.

Si vous utilisez le truc "goto LABEL" que quelqu'un a mentionné et que je dois utiliser votre code, soyez prêt à dormir avec un œil ouvert, surtout si vous le faites plus d'une fois, car ce genre de choses crée horriblement code spaghetti.

Ce n'est pas parce que vous pouvez créer du code spaghetti que vous devriez

13
Kent Fredric

Si le compilateur ne fait aucune optimisation, for(;;) sera toujours plus rapide que while(true). C'est parce que l'instruction while évalue la condition à chaque fois, mais l'instruction for 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 pouvez écrire une boucle infinie comme ceci:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }
9
silverbullettt

Extrait de Stroustrup, TC++ PL (3e édition), §6.1.1:

La curieuse notation for (;;) est la manière standard de spécifier une boucle infinie; vous pouvez le prononcer "pour toujours". [...] while (true) est une alternative.

Je préfère for (;;).

9
Hans W

J'en ai entendu parler une fois.

Il venait d'un programmeur d'AMD Assembly. Il a déclaré que les programmeurs C (les gens) ne réalisent pas que leur code présente des inefficacités. Il a dit aujourd'hui cependant que les compilateurs gcc sont très bons et mettent des gens comme lui à la faillite. Il a dit par exemple, et m'a parlé de 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.

8
Jimmie Clark

Dans une version optimisée d'un langage compilé, il ne devrait pas y avoir de différence appréciable entre les deux. Aucun ne devrait finir par effectuer des comparaisons au moment de l'exécution, ils exécuteront simplement le code de la boucle jusqu'à ce que vous quittiez manuellement la boucle (par exemple avec un break).

5
Charlie

Je suis surpris que personne n'ait 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 se compose pas seulement de la phase d'exécution (qui dans ce cas est la même) mais également de 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 de benchmark pratique que nous pouvons utiliser pour implémenter un benchmark comme 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 infinie pour: une qui est plus courte que la boucle while et une autre qui a un espace supplémentaire pour lui faire 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:

 Taux pour for2 tandis que 
 For 100588/s - -0% -2% 
 For2 100937/s 0% - -1% 
 Tandis que 102147/s 2% 1% - 

La boucle while est clairement gagnante sur cette plateforme.

Sur FreeBSD 8.2 x86_64 avec Perl 5.14.1:

 Taux pour for2 tandis que 
 For 53453/s - -0% -2% 
 For2 53552/s 0% - -2% 
 Tandis que 54564/s 2% 2% - 

Alors que la boucle est gagnante ici aussi.

Sur FreeBSD 8.2 i386 avec Perl 5.14.1:

 Taux tandis que pour for2 
 Tandis que 24311/s - -1% -1% 
 Pour 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 doit être utilisée sur la plate-forme x86_64 si le programmeur optimise la vitesse. Évidemment, une boucle for doit être utilisée lors de l'optimisation de l'espace. Mes résultats ne sont malheureusement pas concluants concernant les autres plateformes.

3
snap

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 optimisateurs, c'est pourquoi vous avez tendance à le voir dans les anciennes bases de code telles que comme commentaire du code source Lions Unix, cependant, à l'ère de l'optimisation des compilateurs, ces gains sont optimisés en couplant avec le fait que ce dernier est plus facile à comprendre que le premier, je pense qu'il serait plus préférable.

2
redbandit

while(1) est un idiome pour for(;;) qui est reconnu par la plupart des compilateurs.

J'étais content de voir que Perl reconnaît également until(0).

2
J M D

Je suis surpris que personne n'ait proposé la forme la plus directe, correspondant à l'assemblage souhaité:

forever:
     do stuff;
     goto forever;
2
Phil Miller

En théorie, un compilateur naïf complètement pourrait stocker le littéral '1' dans le binaire (gaspiller de l'espace) et vérifier si 1 == 0 à chaque itération (perdre du temps et plus d'espace).

En réalité, cependant, 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.

2
Nick T

Je viens de tomber sur ce fil (bien que quelques années en retard).

Je pense avoir trouvé la vraie raison pour laquelle "pour (;;)" est meilleur que "tandis que (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’.

en gros, ce n'est pas un problème de vitesse mais un problème de lisibilité. Selon la police/impression du code, le chiffre un (1) peut ressembler à une lettre minuscule l.

soit 1 vs l. (dans certaines polices, elles semblent identiques).

Ainsi, alors que (1) peut ressembler à une boucle while dépendante de la lettre variable L.

tandis que (true) peut également fonctionner, mais dans certains cas plus anciens en C et en C intégré, true/false n'est pas encore défini, sauf si stdbool.h est inclus.

1
Nick Law