в то время как (1) против для (;;) Есть ли разница в скорости? - PullRequest
149 голосов
/ 20 мая 2009

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

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

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

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

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

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

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

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

Ответы [ 20 ]

214 голосов
/ 20 мая 2009

В 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

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

55 голосов
/ 20 мая 2009

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

L2:
        jmp     L2
53 голосов
/ 20 мая 2009

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

31 голосов
/ 20 мая 2009

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

Для выписки

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

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

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

А 6.5.3 / 2 имеет:

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

Итак, в соответствии со стандартом C ++ код:

for (;;);

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

{
  while (true) {
    ;
    ;
  }
}
28 голосов
/ 20 мая 2009

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

while (1) 

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

for (;;)

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

26 голосов
/ 20 мая 2009

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

20 голосов
/ 23 апреля 2012

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

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

В те дни for( i=10; i; i-- ) был быстрее, чем for( i=1; i <=10; i++ ), поскольку сравнение i равно 0, что приводит к условному переходу CPU-Zero-Flag. И 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
13 голосов
/ 20 мая 2009

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

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

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

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

flow: { 

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

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

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

Обычно это как цикла и что цикла.

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

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

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

9 голосов
/ 02 февраля 2010

От Страуструпа, TC ++ PL (3-е издание), §6.1.1:

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

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

9 голосов
/ 25 марта 2012

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

P.S. Вы можете написать бесконечный цикл, например, такой:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...