В то время как (1) Vs. for (;;) Есть ли разница в скорости?

Длинная версия...

Сотрудник заявил сегодня, увидев мое использование while (1) в Perl script, которое for (;;) работает быстрее. Я утверждал, что они должны быть одинаковыми, надеясь, что интерпретатор будет оптимизировать любые различия. Я настроил script, который запустил бы 1,000,000,000 для итераций цикла и столько же циклов while и записи времени между ними. Я не обнаружил заметной разницы. Мой коллега сказал, что профессор сказал ему, что while (1) делал сравнение 1 == 1, а for (;;) - нет. Мы повторили тот же тест с 100x числом итераций с С++, и разница была незначительной. Тем не менее, это был наглядный пример того, насколько быстрее может быть скомпилированный код и язык сценариев.

Краткая версия...

Есть ли причина предпочесть a while (1) по сравнению с for (;;), если вам нужен бесконечный цикл для выхода из?

Примечание: Если это не ясно из вопроса. Это была просто забавная академическая дискуссия между двумя друзьями. Я знаю, что это не очень важная концепция, которую все программисты должны мучить. Спасибо за все замечательные ответы, которые я (и я уверен, другие) узнали из этого обсуждения несколько вещей.

Обновление: Вышеупомянутый сотрудник был взят с ответом ниже.

Цитируется здесь, если он похоронен.

Это был сборщик программ AMD. Он заявил, что программисты C (poeple) не понимают, что их код имеет неэффективность. Он сказал сегодня, однако, gcc-компиляторы очень хороши и выставляют таких людей как он бизнеса. Он сказал, например, и рассказал мне о while 1 vs for(;;). Я использую это сейчас по привычке, но gcc и особенно переводчики будет выполнять ту же операцию (скачок процессора) для обоих этих дней, поскольку они оптимизированы.

Ответ 1

В perl они приводят к тем же кодам операций:

$ 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

Аналогично в 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

Итак, я думаю, ответ таков: во многих компиляторах они одинаковы. Конечно, для некоторых других компиляторов это может не обязательно иметь место, но, скорее всего, код внутри цикла будет в несколько тысяч раз дороже, чем сам цикл, так что кому это нужно?

Ответ 2

Используя GCC, они оба, похоже, компилируются на один и тот же язык ассемблера:

L2:
        jmp     L2

Ответ 3

Там не так много причин предпочесть друг другу. Я думаю, что while(1) и особенно while(true) более читаемы, чем for(;;), но это только мои предпочтения.

Ответ 4

В соответствии со стандартом нет никакой разницы. 6.5.3/1 имеет:

Оператор for

for ( for-init-statement ; conditionopt ; expressionopt ) statement

эквивалентно

{
  for-init-statement
  while ( condition ) {
    statement
    expression ;
  }
}

И 6.5.3/2 имеет:

Любое или оба условия и выражение могут быть опущены. Отсутствующее условие делает подразумеваемое предложение while эквивалентным while (true).

Итак, согласно стандарту С++, код:

for (;;);

точно такая же, как:

{
  while (true) {
    ;
    ;
  }
}

Ответ 5

Компилятор Visual С++, используемый для выдачи предупреждения для

while (1) 

(постоянное выражение), но не для

for (;;)

Я по-прежнему продолжал практиковать предпочтение for (;;), но я не знаю, продолжает ли это компилятор в наши дни.

Ответ 6

for(;;) - это один символ меньшего характера, если вы хотите идти в этом направлении, чтобы оптимизировать вещи.

Ответ 7

Turbo C с этими старыми компиляторами for(;;) приводит к более быстрому коду, а затем while(1).

Сегодня gcc, Visual C (я думаю, почти все) компиляторы хорошо оптимизируются, а процессоры с частотой 4,7 МГц редко используются.

В те дни for( i=10; i; i-- ) был быстрее, чем for( i=1; i <=10; i++ ), потому что сравнение i равно 0, приводит к условному прыжку с ЦП-нулем. И Zero-Flag был изменен с последней операцией декремента ( i-- ), дополнительная операция cmp не требуется.

    call    __printf_chk
    decl    %ebx          %ebx=iterator i 
    jnz     .L2
    movl    -4(%ebp), %ebx
    leave

и здесь с for(i=1; i<=10; i++) с дополнительным cmpl:

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

Ответ 8

Для всех людей, которые утверждают, что вы не должны использовать indefinte в то время как циклы, и предлагая глупые вещи, как использование open goto (серьезно, ouch)

while (1) {
     last if( condition1 );
     code();
     more_code(); 
     last if( condition2 ); 
     even_more_code(); 
}

Не может быть реально представлен каким-либо другим способом. Не без создания переменной exit и черной магии, чтобы синхронизировать ее.

Если у вас есть склонность к более синтаксису goto-esque, используйте что-то разумное, которое ограничивает область.

flow: { 

   if ( condition ){ 
      redo flow;
   }
   if ( othercondition ){ 
       redo flow;
   }
   if ( earlyexit ){ 
       last flow;
   }
   something(); # doesn't execute when earlyexit is true 
}

В конечном счете скорость не так важна

Повреждение о том, насколько эффективны быстродействующие различные петлевые конструкции, - это огромная трата времени. Преждевременная оптимизация. Я не могу придумать ни одной ситуации, которую я когда-либо видел, где код профилирования обнаруживал узкие места в моем выборе цикла.

Как правило, это как цикл, так и цикл.

Вы должны "оптимизировать" для удобочитаемости и лаконичности и писать все, что лучше всего, чтобы объяснить проблему следующему бедному присоску, который находит ваш код.

Если вы используете трюк "goto LABEL", который кто-то упомянул, и я должен использовать ваш код, будьте готовы спать одним глазом, особенно если вы делаете это более одного раза, потому что этот материал создает ужасный код спагетти.

Просто потому, что вы можете создать код спагетти, не означает, что вы должны

Ответ 9

Из Stroustrup, TС++ PL (3-е издание), §6.1.1:

Любопытная нотация for (;;) - это стандартный способ задания бесконечного цикла; вы можете произнести его "навсегда". [...] while (true) является альтернативой.

Я предпочитаю for (;;).

Ответ 10

Если компилятор не делает никакой оптимизации, for(;;) всегда будет быстрее, чем while(true). Это потому, что while-statement оценивает условие каждый раз, но for-statement является безусловным прыжком. Но если компилятор оптимизирует поток управления, он может генерировать некоторые коды операций. Вы можете легко прочитать код разборки.

P.S. вы можете написать бесконечный цикл следующим образом:

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

Ответ 11

Я слышал об этом один раз.

Это был сборщик программ AMD. Он заявил, что программисты C (люди) не понимают, что их код имеет неэффективность. Он сказал сегодня, хотя, gcc-компиляторы очень хороши, и вытащили таких людей, как он, из бизнеса. Он сказал, например, и рассказал мне о while 1 vs for(;;). Я использую это сейчас по привычке, но gcc и особенно интерпретаторы будут выполнять ту же операцию (скачок процессора) для обоих этих дней, так как они оптимизированы.

Ответ 12

В оптимизированной сборке скомпилированного языка не должно быть заметной разницы между ними. Ни один из них не должен выполнять какие-либо сравнения во время выполнения, они будут просто выполнять код цикла, пока вы не выйдете из цикла вручную (например, с помощью break).

Ответ 13

Я удивлен, что никто не тестировал for (;;) по сравнению с while (1) в perl!

Поскольку perl интерпретируемый язык, время запуска perl script состоит не только из фазы выполнения (которая в этом случае является одинаковой), но и для фазы интерпретации перед выполнением. Обе эти фазы должны учитываться при сравнении скорости.

К счастью, perl имеет удобный модуль Benchmark, который мы можем использовать для реализации теста, например следующего:

#!/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 });

Обратите внимание, что я тестирую две разные версии бесконечного цикла: один, который короче цикла while, и другой, у которого есть дополнительное пространство, чтобы сделать его той же длины, что и цикл while.

В Ubuntu 11.04 x86_64 с perl 5.10.1 Я получаю следующие результаты:

          Rate   for  for2 while
for   100588/s    --   -0%   -2%
for2  100937/s    0%    --   -1%
while 102147/s    2%    1%    --

Цикл while явно является победителем на этой платформе.

В FreeBSD 8.2 x86_64 с perl 5.14.1:

         Rate   for  for2 while
for   53453/s    --   -0%   -2%
for2  53552/s    0%    --   -2%
while 54564/s    2%    2%    --

Хотя цикл также является победителем здесь.

В FreeBSD 8.2 i386 с perl 5.14.1:

         Rate while   for  for2
while 24311/s    --   -1%   -1%
for   24481/s    1%    --   -1%
for2  24637/s    1%    1%    --

Удивительно, что цикл for с дополнительным пространством является самым быстрым выбором здесь!

Я пришел к выводу, что цикл while должен использоваться на платформе x86_64, если программист оптимизирует скорость. Очевидно, цикл for должен использоваться при оптимизации пространства. Мои результаты, к сожалению, неубедительны в отношении других платформ.

Ответ 14

while(1) - это идиома для for(;;), которая распознается большинством компиляторов.

Я был рад видеть, что perl также распознает until(0).

Ответ 15

В теории совершенно наивный компилятор мог бы хранить буквальный '1' в двоичном (пустое пространство) и проверять, есть ли 1 == 0 на каждой итерации (теряя время и больше места).

В действительности, однако, даже при оптимизации "нет" компиляторы все равно уменьшат оба значения. Они также могут выдавать предупреждения, поскольку они могут указывать на логическую ошибку. Например, аргумент while может быть определен где-то в другом месте, и вы не понимаете его постоянным.

Ответ 16

Я удивлен, что никто не предложил более прямую форму, соответствующую желаемой сборке:

forever:
     do stuff;
     goto forever;

Ответ 17

Подводя итог дискуссиям for (;;) vs while (1), очевидно, что первый был быстрее в дни старых не оптимизирующих компиляторов, поэтому вы, как правило, видите его в более старых базовых кодах, таких как Lions Unix Source code комментарий, однако в эпоху оптимизационных компиляторов badass эти выигрыши оптимизированы, что с тем, что последнее легче понять, чем первое, я считаю, что было бы более предпочтительным.

Ответ 18

Я бы подумал, что оба они одинаковы с точки зрения производительности. Но я бы предпочел, чтобы (1) для удобства чтения, но я сомневаюсь, почему вам нужен бесконечный цикл.

Ответ 19

Они одинаковы. Есть гораздо более важные вопросы, чтобы обдумать.