тоді як (1) Vs. for (;;) Чи є різниця швидкостей?


154

Довга версія ...

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

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

Чи є якась причина віддавати перевагу while (1)над «a», for (;;)якщо вам потрібна нескінченна петля?

Примітка: Якщо це не зрозуміло з питання. Це була суто весела академічна дискусія між парою друзів. Я знаю, що це не надто важлива концепція, над якою повинні мучитися всі програмісти. Дякую за всі чудові відповіді, які я (і я впевнений, що інші) дізнався кілька речей з цієї дискусії.

Оновлення: Згаданий вище колега зважився на відповідь нижче.

Цитується тут на випадок, якщо його поховають.

Він прийшов від програміста зі складання AMD. Він заявив, що програмісти C (poeple) не розуміють, що їх код має неефективність. Сьогодні він сказав, що компілятори gcc дуже хороші, і таких людей, як він, виключають з бізнесу. За його словами , наприклад, і розповів мені про while 1проти for(;;). Я використовую його зараз за звичкою, але gcc і, особливо, інтерпретатори будуть робити ту ж операцію (стрибок процесора) і в ці дні, оскільки вони оптимізовані.


4
Мені цікаво. Навіщо потрібен нескінченний цикл у сценарії Perl? Ви, очевидно, не програмуєте драйвер чи системну річ ... Нескінченно довго тихо :-)
Люк М

125
Яка нескінченна петля є найшвидшою? LOL ... "Мій новий комп’ютер настільки швидкий, він працює нескінченним циклом лише за годину ..." ;-)
Arjan Einbu

8
Це був професор соціології, який сказав йому це? У сучасну епоху введений вами код - це не те, що бачить комп'ютер.
Брайан d foy

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

Тож я здогадуюсь, що відповідь така: вони однакові у багатьох компіляторів. Звичайно, для деяких інших компіляторів це не обов'язково так, але ймовірність того, що код всередині циклу буде в кілька тисяч разів дорожчим, ніж сам цикл, так кому це все одно?


15
спробуйте з B :: Deparse, відходить нескінченна петля, яка повертає цикл: P
Кент Фредрік

27
"У perl вони призводять до однакових опкодів" ... так, але що швидше? :-)
Олов'яний чоловік

6
Мені подобається, що gcc підміняє put () для printf (), оскільки є лише один аргумент, і тому нічого форматувати - швидше і безпечніше! (gcc також перевіряє форматування тегів у списку аргументів змінної.)
Лі 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 має:

Будь-або умова та вираз можуть бути опущені. Відсутня умова робить застереження, що мається на увазі, прирівнюється до параметра true (true).

Отже, згідно стандарту C ++ код:

for (;;);

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

{
  while (true) {
    ;
    ;
  }
}

4
Це взагалі не стосується створеного коду чи продуктивності. Стандарт визначає лише функціональність. Звичайно, продуктивність буде однаковою.
Potatoswatter

1
Я не вірю, що це правда, що різниця в продуктивності порушує правило нібито. Якби це було, тоді компіляторам не дозволялося б прискорювати ваш код за правилом as-if, наприклад, шляхом переупорядкування незалежних операторів. Компілятори насправді роблять саме це. Але моя копія стандарту - це шлях наверх.
Стів Джессоп

28

Компілятор Visual C ++ використовується для надсилання попередження для

while (1) 

(постійний вираз), але не для

for (;;)

Я продовжував практику вподобання for (;;)з цієї причини, але не знаю, чи компілятор все ще робить це в наші дні.


попередження, ймовірно, тому що ви використовували, коли (1) замість while (правда)
jrharshath

16
істина - константа. в той час як (істина) - постійний вираз. Для всіх, хто цікавиться, тут задокументовано попередження C4127: msdn.microsoft.com/en-us/library/6t66728h(VS.80).aspx
sean e

Так, попередження все ще існує як для 1, так і для істинного. Ось чому я завжди використовую для (;;)
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. І Нульовий прапор був змінений під час останньої операції декременту ( 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

Для всіх людей, які стверджують, ви не повинні використовувати indefinte під час циклів, і пропонуючи дафтові речі, як-от використання open goto 's (серйозно, ой)

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

Неможливо насправді представити іншим способом. Не без створення вихідної змінної та виконання чорної магії для її синхронізації.

Якщо у вас є схильність до більш синтаксису 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, TC ++ PL (3-е видання), §6.1.1:

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

Я віддаю перевагу for (;;).


9

Якщо компілятор не робить оптимізації, for(;;)завжди буде швидше, ніжwhile(true) . Це тому, що while-statement щоразу оцінює умову, а для твердження - безумовний стрибок. Але якщо компілятор оптимізує потік управління, він може генерувати деякі опкоди. Ви можете прочитати код розбирання дуже легко.

PS ви можете написати нескінченний цикл так:

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

У сучасний день та вік НІКОЛИ не слід замінювати EVS (підлітковий розмову)! Серйозно, хоча я просто використовую для (;;) {}. Я давно читав в Інтернеті про відмінності між ними (коли я був молодшим і насправді не знав, що вони однакові) і просто затримався з тим, що я прочитав.
Бья

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 });

Зауважте, що я тестую дві різні версії нескінченного циклу: одна, яка коротша, ніж цикл while, та інша, яка має додатковий простір, щоб зробити її такою ж довжиною, як цикл while.

У Ubuntu 11.04 x86_64 з perl 5.10.1 я отримую такі результати:

          Оцініть на 2, а час
для 100588 / с - -0% -2%
для2 100937 / с 0% - -1%
тоді як 102147 / с 2% 1% -

Цикл while явно є переможцем на цій платформі.

У FreeBSD 8,2 x86_64 з Perl 5.14.1:

         Оцініть на 2, а час
для 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% -

Дивно, але цикл з додатковим простором - це найшвидший вибір тут!

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


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;

Доза, що не закінчується тим же машинним кодом, що і під час 1 або для (;;) у скажімо, c?
Copas

1
Ще один недолік цього підходу: він порушує інкапсуляцію, не замикаючи цикл у блоці - таким чином, будь-які змінні, оголошені у циклі, є поза циклом. (Звичайно, ви могли { forever: do stuff; goto forever; })
Рой Тінкер

2

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

Я був радий бачити, що perl це until(0)теж визнає .


У якій ситуації було б корисним (0)?
Копас

3
поки () не є протилежною while () так само, як, якщо if () не є протилежною if (). Як запропоновано десь у цій темі, можна написати: роби {щось ...} в той час як (! Умова) Альтернативою може бути до (умова) {щось}
JMD

2

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


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 проти л. (у деяких шрифтах вони виглядають однаково).

Отже, хоча (1) може виглядати як деякий, а цикл залежить від змінної букви L.

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


2
Я б сказав, що проблема у вашому коді полягає в тому, що у вас є названа змінна l, а не така 1і lможе виглядати схоже.
mjuopperi

Погоджено, я знаю, що стандарт кодування Барра також в інших місцях говорить, що змінні повинні містити принаймні 3 символи навіть для циклів. тобто немає i ++ тощо в циклі for. Я схильний вважати, що це може бути трохи набагато. Під час введення тексту я також помічаю, що не просто літера L виглядає як 1. Буква i, яка зазвичай використовується як змінна, також може викликати проблеми.
Нік

-3

Я б подумав, що обидва однакові за показниками продуктивності. Але я вважаю за краще читати (1), але я сумніваюся, для чого вам потрібна нескінченна петля.


-14

Вони однакові. Є набагато важливіші питання, над якими можна розмірковувати.


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


22
Ніяких посилань чи пояснень. Непомітний, суб’єктивний і трохи поблажливий.
cdmckay

1
ну ніяких доказів, але він правий. Вони обидва називають Opcode за стрибки, коли неправдиві. (що зробило б це так само, як goto, але ніхто не любить готос)
Matthew Whited

3
Мені не було відомо, що лише мої важливі запитання, де мене запитувати, моя помилка була моїм першим запитанням.
Копас

3
Так, я визнаю, це поблажливо. Але якщо серйозно, навіть без будь-яких доказів очевидно, що вони будуть знаходитися в тому ж самому бальному парку швидко; якби питання стосувалося стилю, було б про що сперечатися. Я намагався зауважити, що у списку речей, про які потрібно хвилювати, це справді має бути внизу списку.
Марк Рансом

8
Я не намагався бути дурнем. Я намагався зробити крапку. Коли я опублікував це, я намагався отримати якийсь темний гумор, і очевидно, що я провалився; за це я вибачаюся.
Марк Викуп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.