В то время как (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
Они одинаковы. Есть гораздо более важные вопросы, чтобы обдумать.