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

154

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

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

Укороченная версия...

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

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

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

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

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

Copas
источник
4
Мне любопытно. Зачем нужен бесконечный цикл в Perl-скрипте? Вы, очевидно, не программируете драйвер или системные вещи ... Infinite долго молчит :-)
Luc M
125
Какой бесконечный цикл самый быстрый? LOL ... "Мой новый компьютер так быстр, он запускает бесконечный цикл всего за час ..." ;-)
Arjan Einbu
8
Это профессор социологии сказал ему об этом? В современную эпоху код, который вы вводите, - это не то, что компьютер видит.
Брайан Д. Фой
5
я ожидаю, что количество времени, которое понадобилось вам, чтобы проверить это, было намного больше, чем количество времени, которое потенциально можно сэкономить, зная, какой из них быстрее, если и тот, и другой. даже если вы амортизируете это в течение обеих ваших жизней программирования.
Питер Рекор
4
Зачем компилятору генерировать код для выполнения теста, который, как он знает, не имеет побочных эффектов и чей результат компилятор уже знает? Это не имеет смысла.
Дэвид Шварц

Ответы:

218

В 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

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

bdonlan
источник
15
попробуйте с B :: Deparse, отмена бесконечного цикла for возвращает цикл while: P
Кент Фредрик
27
«В perl они приводят к одинаковым кодам операций» ... да, но что быстрее? :-)
Жестянщик
6
Мне нравится, что gcc замещает put () для printf (), так как есть только один аргумент и, следовательно, ничего форматировать - быстрее и безопаснее! (gcc также проверяет теги форматирования по списку переменных аргументов.)
Lee D
@ The Tin Man: они эквивалентны, потому что компьютер выполняет точно такие же операции: P
BlackBear
1
@snap, это не «полностью» неверно, оно просто фокусируется на затратах времени выполнения. Я не могу представить, какая ситуация приведет к тому, что время анализа бесконечных циклов станет ключевым фактором, определяющим, насколько быстро работает ваша программа
bdonlan
55

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

L2:
        jmp     L2
Мартин Кот
источник
20
Использование GCC с опцией -S (собирать, не связывать)
Martin Cote
54

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

Билл Ящерица
источник
94
#define EVER ;; для (КОГДА-ЛИБО) я всегда нахожу такого рода забавным.
Том
19
Как насчет #define ever (;;) навсегда;
Мартин Кот
16
Оба кажутся более читаемыми на поверхности, но я стараюсь не определять новые ключевые слова для моего программиста по обслуживанию (обычно меня), чтобы почесать голову.
Билл Ящерица
13
@Martin, который не будет работать, потому что # define не заменяют в токене, и foreverэто его собственный токен.
Лили Чунг
2
«Я стараюсь не определять новые ключевые слова для моего обслуживания» - если бы только больше людей приняли такое отношение, я бы не стал цепляться за все эти бессмысленные и волшебные махинации с ловкостью рук каждый раз, когда я оборачивался!
tchrist
31

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

Компилятор Visual C ++ используется для предупреждения

while (1) 

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

for (;;)

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

Шон Э
источник
предупреждение, вероятно, потому что вы использовали while (1) вместо while (true)
jrharshath
16
Истина это константа. в то время как (истина) является константным выражением. Для всех, кто заинтересован, предупреждение C4127 задокументировано здесь: msdn.microsoft.com/en-us/library/6t66728h(VS.80).aspx
Шон
Да, предупреждение все еще присутствует как для 1, так и для true. Это причина, почему я всегда использую для (;;)
Elviss Strazdins
26

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

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

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

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

В те дни a 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

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

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

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

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

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

Ханс В
источник
9

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

PS вы могли бы написать бесконечный цикл, как это:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }
silverbullettt
источник
В наше время и никогда не должны быть заменены EVS (подростковый говорить)! Серьезно, хотя я просто использую для (;;) {}. Я давно читал в Интернете о различиях между ними (когда я был моложе и фактически не знал, что они одинаковые), и просто увлекся прочитанным.
Bja
8

Я слышал об этом однажды.

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

Джимми Кларк
источник
5

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

Чарли
источник
3

Я удивлен , что никто должным образом не тестировался в for (;;)сравнении while (1)с Perl!

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

К счастью, в 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 });

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

На Ubuntu 11.04 x86_64 с Perl 5.10.1 я получаю следующие результаты:

          Оценить для for2 время
для 100588 / с - -0% -2%
для 2 100937 / с 0% - -1%
в то время как 102147 / с 2% 1% -

Цикл while явно выиграл на этой платформе.

На FreeBSD 8.2 x86_64 с Perl 5.14.1:

         Оценить для for2 время
для 53453 / с - -0% -2%
для 2 53552 / с 0% - -2%
в то время как 54564 / с 2% 2% -

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

На FreeBSD 8.2 i386 с Perl 5.14.1:

         Оцените пока для for2
при 24311 / с - -1% -1%
для 24481 / с 1% - -1%
для 2 24637 / с 1% 1% -

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

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

щелчок
источник
9
Вывод явно ошибочен. Benchmarkимеет свои ограничения и не может использоваться для отличия быстрого от медленного, если результаты находятся в пределах 7% друг от друга. Кроме того, вы не испытали разницу между forи whileпетлями , потому что каждый суб будет dieдо достижения самих петель. И с каких это пор количество пробелов имеет значение для интерпретатора Perl? Извините, но анализ крайне некорректен.
Заид
2
@Zaid, спасибо за ваши комментарии! Не могли бы вы опубликовать свой собственный ответ, чтобы каждый мог извлечь уроки из этого? :) В dieмоем коде есть, потому что я собираюсь проверить только разницу во времени компиляции . Как уже отмечали другие, полученный байт-код идентичен, поэтому нет смысла проверять это. Удивительно, но количество пробелов в этом случае незначительно меняет мои тестовые среды. Это может иметь какое-то отношение к тому, как персонажи оказываются выровненными в памяти или что-то подобное ...
оснастка
4
Мне не нужно публиковать ответ, потому что то, что я бы сказал, уже упоминалось bdonlan. И даже если вы сравниваете время компиляции, цифры Benchmarkнеубедительны. Не верьте этой разнице в 1%!
Заид
Всего 60 итераций? Выполните тесты примерно на 5 минут, чтобы получить более точное относительное время.
Mooing Duck
-60запускает тест в течение 60 секунд.
оснастка
2

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

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

Ник Т
источник
2

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

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

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

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

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

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

redbandit
источник
2

Просто наткнулся на эту ветку (хотя и опоздал на несколько лет).

Я думаю, что нашел реальную причину, по которой «for (;;)» лучше, чем «while (1)».

в соответствии со «стандартом кодирования 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’.

в основном это не проблема скорости, а проблема читабельности. В зависимости от шрифта / печати кода число один (1) через некоторое время может выглядеть как строчная буква l.

т.е. 1 против л. (в некоторых шрифтах они выглядят одинаково).

Так что while (1) может выглядеть как цикл while, зависящий от переменной L.

Хотя (true) также может работать, но в некоторых старых случаях C и встроенного C true / false еще не определены, если не включен stdbool.h.

Ник Лоу
источник
2
Я бы сказал, что проблема в вашем коде заключается в том, что у вас есть переменная с именем l, а не та, 1и она lможет выглядеть аналогично.
mjuopperi
Согласен, я знаю, что в стандарте кодирования Барра также говорится, что переменные должны содержать не менее 3 символов даже для циклов. т.е. нет я ++ и т. д. в цикле. Я склонен думать, что это может быть немного много, хотя. При наборе текста я также замечаю, что это не просто буква L, которая выглядит как 1. Буква i, которая обычно используется в качестве переменной, также может вызывать проблемы.
Ник Лоу
-3

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

bichonfrise74
источник
-14

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


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

Марк Рэнсом
источник
22
Нет ссылок или объяснений. Бесполезный, субъективный и немного снисходительный.
cdmckay
1
ну нет доказательств, но он прав. Они оба вызывают код операции для прыжка, когда ложь. (что делает его таким же, как goto, но никто не любит gotos)
Мэтью Уайтед
3
Я не знал, что только важные вопросы, где можно задать, моя ошибка была моим первым вопросом.
Копас
3
Да, я признаю, что это снисходительно. А если серьезно, даже без каких-либо доказательств очевидно, что они будут находиться на одной площадке быстро; если бы вопрос был о стиле, было бы о чем спорить. Я пытался подчеркнуть, что в списке вещей, о которых стоит беспокоиться, это действительно должно быть внизу списка.
Марк Рэнсом
8
Я не пытался быть придурком. Я пытался сделать точку. Когда я опубликовал это, я пытался создать своего рода темный юмор, и очевидно, что я потерпел неудачу; за что я прошу прощения.
Марк Рэнсом