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

132

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

Сотрудник заявил сегодня, увидев мое использование 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 и особенно переводчики будет выполнять ту же операцию (скачок процессора) для обоих этих дней, поскольку они оптимизированы.

  • 4
    Мне любопытно. Зачем нужен бесконечный цикл в Perl-скрипте? Вы, очевидно, не программируете драйвер или системные вещи ... Infinite долго молчит :-)
  • 1
    Это сценарий, который предназначен для запуска на неопределенное время (из-за отсутствия лучшего / более подробного объяснения) на встроенном оборудовании и что-то делает каждые 5 секунд.
Показать ещё 16 комментариев
Теги:
optimization
performance

19 ответов

205
Лучший ответ

В 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

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

  • 14
    попробуйте с B :: Deparse, если бесконечный цикл for возвращает цикл while: P
  • 22
    «В perl они приводят к одним и тем же кодам операций» ... да, но что быстрее? :-)
Показать ещё 5 комментариев
54

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

L2:
        jmp     L2
  • 3
    Как вы получаете этот код сборки?
  • 18
    Использование GCC с опцией -S (собрать, не связывать)
49

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

  • 90
    #define EVER ;; для (КОГДА-ЛИБО) я всегда нахожу такого рода забавным.
  • 18
    Как насчет #define ever (;;) навсегда;
Показать ещё 6 комментариев
30

В соответствии со стандартом нет никакой разницы. 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) {
    ;
    ;
  }
}
  • 4
    Это не относится к сгенерированному коду или производительности вообще. Стандарт определяет только функциональность. Конечно, производительность будет такой же.
  • 1
    Я не верю, что это правда, что разница в производительности нарушает правило «как будто». Если бы это было так, то компиляторам было бы запрещено ускорять ваш код в соответствии с правилом «как будто», например, путем переупорядочения независимых операторов. Компиляторы фактически делают именно это. Но моя копия стандарта далеко наверху.
28

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

while (1) 

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

for (;;)

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

  • 0
    Спасибо! Это первая веская причина недоразумения моих друзей, которое я слышал.
  • 0
    предупреждение, вероятно, потому что вы использовали while (1) вместо while (true)
Показать ещё 2 комментария
22

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

  • 21
    Полезно знать для игры в гольф. В противном случае плохая причина для выбора синтаксиса.
  • 0
    @AdamBellaire Terseness часто повышает читаемость, превышая определенный порог навыка.
21

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
  • 5
    Я не могу поверить, что после двух лет этот несправедливый вопрос все еще получает вдумчивые, качественные ответы. Благодарю.
13

Для всех людей, которые утверждают, что вы не должны использовать 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", который кто-то упомянул, и я должен использовать ваш код, будьте готовы спать одним глазом, особенно если вы делаете это более одного раза, потому что этот материал создает ужасный код спагетти.

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

8

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

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

#define EVER ;;
  //...
  for (EVER) {
    //...
  }
  • 1
    Huehue мне нравится для (когда-либо). +1 за эстетику.
  • 0
    В наше время не должно быть НИКОГДА заменено EVS (подростковая речь)! Серьезно, хотя я просто использую для (;;) {}. Я давно читал в Интернете о различиях между этими двумя (когда я был моложе и фактически не знал, что они одинаковы), и просто придерживался того, что я читаю.
8

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

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

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

7

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

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

5

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

3

Я удивлен, что никто не тестировал 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 должен использоваться при оптимизации пространства. Мои результаты, к сожалению, неубедительны в отношении других платформ.

  • 9
    Вывод явно ошибочен. Benchmark имеет свои ограничения и не может использоваться для отличия быстрого от медленного, если результаты находятся в пределах 7% друг от друга. Кроме того, вы не проверили разницу между циклами for и while потому что каждый саб die до того, как достигнет самих циклов. И с каких это пор количество пробелов имеет значение для интерпретатора Perl? Извините, но анализ крайне некорректен.
  • 2
    @Zaid, спасибо за ваши комментарии! Не могли бы вы опубликовать свой ответ, чтобы каждый мог извлечь уроки из этого? :) В моем коде есть die потому что я собираюсь проверить только разницу во времени компиляции . Как уже отмечали другие, полученный байт-код идентичен, поэтому нет смысла проверять это. Удивительно, но количество пробелов в этом случае незначительно меняет ситуацию в моих средах тестирования. Это может быть связано с тем, как персонажи выстраиваются в памяти или что-то подобное ...
Показать ещё 3 комментария
2

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

2

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

forever:
     do stuff;
     goto forever;
  • 0
    Доза, которая не заканчивается тем же машинным кодом, что и while 1 или for (;;) в скажем c?
  • 1
    Еще один недостаток этого подхода: он нарушает инкапсуляцию, не заключая цикл в блок - поэтому любые переменные, объявленные в цикле, доступны вне цикла. (Конечно, вы могли бы { forever: do stuff; goto forever; } )
2

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

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

2

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

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

  • 0
    В какой ситуации будет полезно (0)?
  • 3
    while () противоположно while () так же, как и если () не противоположно if (). Как было предложено ранее в этой теме, можно написать: do {кое-что ...} while (! Условие) Альтернативой может быть до (условие) {что-то}
-1

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

-7

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

  • 20
    Нет ссылок или объяснений. Бесполезно, субъективно и немного снисходительно.
  • 1
    ну нет доказательств, но он прав. Они оба вызывают код операции для прыжка, когда ложь. (что делает его таким же, как goto, но никто не любит gotos)
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню