Що швидше: поки (1) або поки (2)?


587

Це було питання інтерв'ю, яке йому задав вищий менеджер.

Що швидше?

while(1) {
    // Some code
}

або

while(2) {
    //Some code
}

Я сказав, що обидва мають однакову швидкість виконання, оскільки вираз усередині whileповинен нарешті оцінити до trueабо false. У цьому випадку обидва оцінюють trueта не містять додаткових умовних інструкцій всередині whileумови. Отже, обидва матимуть однакову швидкість виконання, і я вважаю за краще (1).

Але інтерв'юер впевнено сказав: "Перевірте основи. while(1)Це швидше, ніж while(2)". (Він не перевіряв мою впевненість)

Це правда?

Дивіться також: Чи "for (;;)" швидше, ніж "while (TRUE)"? Якщо ні, то чому люди його використовують?


202
Напівпристойний компілятор оптимізує обидві форми ні до чого.

64
В оптимізованій збірці кожен раз (n), n! = 0 або for (;;) буде переведено на нескінченний цикл складання з міткою на початку і перейти в кінці. Точно той самий код, однакова продуктивність.
Алекс Ф

60
Не дивно, що оптимізація запасів приносить 0x100000f90: jmp 0x100000f90(очевидно, що різниця) для обох фрагментів. Інтерв'юер, ймовірно, хеджував тест реєстру проти простого стрибка. І питання, і їхні припущення - кульгаві.
WhozCraig

50
Це запитання інтерв'юера підпадає під ті ж егіди, що і dilbert.com/strips/comic/1995-11-17 - ви зустрінете когось, хто справді вірить у те, що вони говорять, незалежно від коефіцієнта дурості у своїй заяві. Просто виберіть одне з наступних: глибоко
дихайте, лаяться

3
@Mike W: можна задатися питанням, що повинен робити компілятор: перевести на оператор Halt або вважати, що цикл виходить через нескінченний час та оптимізує нескінченну затримку?
Ів Дауст

Відповіді:


681

Обидві петлі нескінченні, але ми можемо побачити, яка з них потребує більше інструкцій / ресурсів за ітерацію.

Використовуючи gcc, я склав дві наступні програми для збирання з різними рівнями оптимізації:

int main(void) {
    while(1) {}
    return 0;
}


int main(void) {
    while(2) {}
    return 0;
}

Навіть не маючи оптимізацій ( -O0), згенерована збірка була однаковою для обох програм . Тому різниці швидкостей між двома петлями немає.

Для довідки, ось створена збірка (використовуючи gcc main.c -S -masm=intelпрапор оптимізації):

З -O0:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    push    rbp
    .seh_pushreg    rbp
    mov rbp, rsp
    .seh_setframe   rbp, 0
    sub rsp, 32
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

З -O1:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

З -O2і -O3(той же вихід):

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Насправді збірка, що генерується для циклу, однакова для кожного рівня оптимізації:

 .L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Важливі біти:

.L2:
    jmp .L2

Я не можу читати складання дуже добре, але це, очевидно, безумовний цикл. jmpІнструкція беззастережно скидає програму назад на .L2етикетці, навіть НЕ порівнюючи значення проти істини, і, звичайно , відразу ж робить це знову , поки програма не буде якимось - то чином закінчилася. Це безпосередньо відповідає коду C / C ++:

L2:
    goto L2;

Редагувати:

Цікаво, що навіть без оптимізації , наступні цикли дають такий же вихід (безумовний jmp) у збірці:

while(42) {}

while(1==1) {}

while(2==2) {}

while(4<7) {}

while(3==3 && 4==4) {}

while(8-9 < 0) {}

while(4.3 * 3e4 >= 2 << 6) {}

while(-0.1 + 02) {}

І навіть на мій подив:

#include<math.h>

while(sqrt(7)) {}

while(hypot(3,4)) {}

Речі стають трохи цікавішими за допомогою визначених користувачем функцій:

int x(void) {
    return 1;
}

while(x()) {}


#include<math.h>

double x(void) {
    return sqrt(7);
}

while(x()) {}

У -O0цих двох прикладах насправді виклик xта порівняння для кожної ітерації.

Перший приклад (повернення 1):

.L4:
    call    x
    testl   %eax, %eax
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Другий приклад (повернення sqrt(7)):

.L4:
    call    x
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jp  .L4
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Однак -O1вгорі і вище вони обидва виробляють таку ж збірку, що і попередні приклади (безумовне jmpповернення до попередньої мітки).

TL; DR

Під GCC різні петлі складаються на однакові збірки. Компілятор оцінює постійні значення і не заважає виконувати фактичне порівняння.

Мораль історії полягає в наступному:

  • Існує шар перекладу між вихідним кодом C ++ та інструкціями CPU, і цей шар має важливі наслідки для продуктивності.
  • Отже, продуктивність не може бути оцінена лише переглядом вихідного коду.
  • Компілятор повинен бути досить розумним, щоб оптимізувати такі тривіальні випадки. Програмісти не повинні витрачати свій час на роздуми про них у переважній більшості випадків.

196
Можливо, інтерв'юер не використовував gcc
MM

106
@Matt McNabb Це хороший момент, але якщо інтерв'юер покладався на конкретні оптимізації для компілятора, то їм потрібно бути чітко про це в їхньому питанні, і їм потрібно прийняти відповідь "немає різниці" як правильну для деякі (більшість?) компіляторів.
Джонатан Хартлі

109
Для того, щоб усунути будь-які сумніви, я перевірив це в клапсі 3.4.2, і обидві петлі виробляють однакові збірки на кожному -Oрівні.
Мартін Турноїй

16
Мене це не дивно, оскільки все, що ви розмістили в умовному розділі циклів, - константи часу компіляції. Як такий, я б підозрював, що компілятор побачить, що петлі завжди будуть істинними або хибними, або респективно просто jmpповертаються до початку або повністю видаляють цикл.
sherrellbc

10
@hippietrail Я не бачу, як вміст (або його відсутність) циклу може вплинути на ці оптимізації (крім можливості будь-яких breakвисловлювань), але я просто перевірив це все одно і ні, навіть з кодом всередині циклу, стрибок абсолютний і безумовний і для, while(1)і для while(2). Не соромтеся перевірити інших, якщо ви насправді переймаєтесь.
Наближається до

282

Так, while(1)набагато швидше , ніж while(2), для людини , щоб читати! Якщо я бачу while(1)в незнайомій кодовій базі, я одразу знаю, що задумав автор, і мої очні яблука можуть перейти до наступного рядка.

Якщо я побачу while(2), я, мабуть, зупиняться на своїх слідах і спробую з’ясувати, чому автор не написав while(1). Чи палив авторський палець по клавіатурі? Чи користувачі цієї кодової бази використовують while(n)як незрозумілий механізм коментування, щоб петлі виглядали інакше? Це грубе вирішення несправжнього попередження в якомусь зламаному інструменті статичного аналізу? Або це підказка, що я читаю згенерований код? Це помилка внаслідок непродуманого пошуку і заміни всіх, або поганого злиття, або космічного променя? Можливо, цей рядок коду повинен зробити щось кардинально інше. Можливо, це слід було прочитати while(w)або while(x2). Я б краще знайти автора в історії файлу і надіслати їм електронний лист "WTF" ... і тепер я порушив свій ментальний контекст.while(2)while(1) зайняв би частку секунди!

Я перебільшую, але лише небагато. Читання коду дійсно важливо. І це варто згадати в інтерв'ю!


Мої думки точно, і саме тому, хоча (1) швидше хахаю Хоча я думаю, що це був просто старий випадок, коли менеджер намагається переконати себе, що він знає про кодування, коли він цього не робить, ми все це бачили, кожен хоче бути джеді.
екернер

8
Абсолютно, це зовсім НЕ перебільшення. Однозначно тут буде використовуватися svn-annotate або git blame(або будь-що інше), і загалом потрібно тривати кілька хвилин, щоб завантажити історію звинувачення у файлі. Тоді, нарешті, щоб вирішити "ах, я розумію, автор написав цей рядок, коли свіжий із середньої школи", просто втратив 10 хвилин ...
v.oddou

11
Проголосував за те, що мені нудно конвенцій. У розробника є така неповторна можливість написати номер, який йому подобається, тоді хтось нудний приходить і нудить про те, чому це не так 1.
Utkan Gezer

1
Захищений через риторику. Читання, коли (1), є точно такою ж швидкістю, що і під час (2).
hdante

4
Я пройшов такий самий ментальний процес, як описаний у цій відповіді хвилину тому, коли побачив while(2)у коді (аж до розгляду "Чи користуються сервіси цієї кодової бази в той час, як (n), як незрозумілий механізм коментування, щоб петлі виглядали інакше?" ). Так ні, ти не перебільшуєш!
Hisham HM

151

Існуючі відповіді, що показують код, сформований певним компілятором для певної цілі з певним набором опцій, не відповідають повністю на питання - якщо тільки запитання не було задано у конкретному контексті ("Що швидше використовувати gcc 4.7.2 для x86_64 з опціями за замовчуванням? ", наприклад).

Що стосується визначення мови, то в абстрактній машині while (1) оцінюється ціла константа 1та while (2)оцінюється ціла константа 2; в обох випадках результат порівнюється для рівності нулю. Мовний стандарт абсолютно нічого не говорить про відносну ефективність двох конструкцій.

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

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

int n = 4;
switch (n) {
    case 2+2: break;
    case 4:   break;
}

вимагає діагностики; лінивий компілятор не має можливості відкладати оцінку 2+2до часу виконання. Оскільки компілятор повинен мати можливість оцінювати постійні вирази під час компіляції, немає жодної вагомої причини для того, щоб він не скористався цією можливістю, навіть коли це не потрібно.

Стандарт C ( N1570 6.8.5p4) говорить про це

Оператор ітерації призводить до того, що оператор, який називається тілом циклу, виконується повторно, поки керуючий вираз не порівнюється з 0.

Отже, відповідні постійні вирази є 1 == 0і 2 == 0обидва оцінюють за intзначенням 0. (Ці порівняння неявні в семантиці whileциклу; вони не існують як фактичні вирази С.)

Збочно наївний компілятор може створити різний код для двох конструкцій. Наприклад, для першого він може створити безумовний нескінченний цикл (трактуючи 1як окремий випадок), а для другого він може створити явне порівняння часу виконання, еквівалентне 2 != 0. Але я ніколи не стикався з компілятором С, який би насправді так поводився, і я серйозно сумніваюся, що такий компілятор існує.

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

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

Підсумок: while (1)і while(2) майже напевно мають однакові показники. Вони мають однакову семантику, і немає жодної вагомої причини жоден компілятор не генерувати однаковий код.

І хоча це абсолютно законно для компілятора, щоб генерувати швидший код для, while(1)ніж для while(2), це однаково законно для компілятора, щоб генерувати швидший код для, while(1)ніж для іншого появи while(1)в тій же програмі.

(Існує ще одне запитання, яке мається на увазі в тому, що ви задали: Як ви маєте справу з інтерв'юером, який наполягає на неправильному технічному питанні. Це, мабуть, буде гарним питанням для сайту Workplace ).


8
"У цьому випадку відповідні (неявні) постійні вирази дорівнюють 1! = 0 і 2! = 0, обидва з яких оцінюють до значення int 1" ... Це надмірно складно і неточно. Стандарт просто говорить, що керуючий вираз whileмає бути скалярного типу, і тіло циклу повторюється, поки вираз не порівняється з рівним 0. Він не говорить про те, що існує неявна expr != 0оцінка, яка оцінюється ... що вимагає результату що - 0 або 1 - у свою чергу порівнюється з 0, ad infinitum. Ні, вираз порівнюється з 0, але це порівняння не дає значення. PS Я підтримав.
Джим Балтер

3
@JimBalter: Я бачу вашу думку, і я оновлю свою відповідь, щоб вирішити її. Однак я мав на увазі те, що формулювання стандарту "... поки керуючий вираз не порівнюється з рівним 0" передбачає оцінку <expr> == 0; це те, що «можна порівняти дорівнює 0» кошти в C. Це порівняння є частиною семантики whileциклу. Ні в стандарті, ні в моїй відповіді немає жодного наслідку, що результат потрібно порівнювати 0ще раз. (Я повинен був писати, ==а не писати !=.) Але ця частина моєї відповіді була незрозумілою, і я її оновлю.
Кіт Томпсон

1
"" порівнюється рівним 0 "означає C" - але ця мова є у стандарті , а не в C ... реалізація робить порівняння, вона не генерує порівняння мови С. Ви пишете "обидва з яких оцінюють до int значення 1", але такої оцінки ніколи не відбувається. Якщо ви подивіться на код , створений для while (2), while (pointer), while (9.67), найнаївнішим, неоптимізованими компілятором, ви не побачите жодного коду , згенерованого , що врожайність 0або 1. "Я повинен був писати ==, а не! =" - ні, це не мало б сенсу.
Джим Балтер

2
@JimBalter: Хм. Я не маю на увазі припускати, що "порівняння дорівнює 0" передбачає існування ... == 0виразу С. Моя думка полягає в тому, що і "порівнювання, рівне 0", що вимагається описом whileциклу стандарту, і явне x == 0вираження логічно передбачають ту саму операцію. І я думаю, що болісно наївний компілятор C може генерувати код, який генерує intзначення 0або 1для будь-якого whileциклу - хоча я не вірю, що жоден фактичний компілятор є таким наївним.
Кіт Томпсон


141

Почекай хвилинку. Інтерв'юер, чи схожий він на цього хлопця?

введіть тут опис зображення

Досить погано, що сам інтерв'юер провів це інтерв'ю, що робити, якщо інші програмісти цієї компанії "пройшли" цей тест?

Ні. Оцінка тверджень 1 == 0і 2 == 0 повинна бути однаково швидкою. Ми можемо уявити погані реалізації компілятора, де одна може бути швидшою, ніж інша. Але немає вагомих причин, чому один повинен бути швидшим, ніж інший.

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

Відмова: Це НЕ оригінальний мультфільм Ділберта. Це просто машу .


6
Це було б смішно, якби воно також містило відповідь на запитання.
Коді Грей

ні, але насправді, ми можемо досить легко уявити, що всі компілятори, написані серйозними компаніями, створюватимуть розумний код. візьмемо "неоптимізований випадок" / O0, можливо, він закінчиться так, як розмістив анатоліг. Тоді це питання процесора, чи буде cmpоперанд працювати менше циклів, порівнюючи від 1 до 0, ніж 2 до 0? скільки циклів потрібно для виконання cmp взагалі? чи змінна вона відповідно до бітових моделей? вони більш "складні" бітові моделі, які сповільнюються cmp? Я особисто не знаю. ви можете уявити собі супер ідіотичну реалізацію, що перевіряє біт за бітом від рангу 0 до n (наприклад, n = 31).
v.oddou

5
У цьому і моя думка: cmpоперанд повинен бути однаково швидким для 1 і 200. Напевно, ми могли б уявити ідіотичні реалізації, де це не так. Але чи можемо ми уявити неідіотичну реалізацію, де while(1)швидше, ніж while(200)? Так само, якби в якийсь доісторичний вік єдиною доступною реалізацією було подібне ідіотичне, чи варто ми сьогодні метушимось про це? Я не думаю, що це гостроволоса розмова боса, і справжній дорогоцінний камінь!
janos

@ v.ouddou "чи буде операнд cmp працювати менше циклів, порівнюючи від 1 до 0, ніж 2 до 0" - Ні. Ви повинні дізнатися, що таке цикл. "Я особисто не знаю. Ви могли б уявити собі супер-ідіотичну реалізацію, яка перевіряє поступово від рангу 0 до n" - або іншим способом, все ще роблячи інтерв'юера безглуздим ідіотом. І навіщо турбуватися про перевірку потроху? Реалізація може бути людиною в коробці, яка вирішить зробити перерву на обід посеред оцінювання вашої програми.
Джим Балтер

79

Ваше пояснення правильне. Це, здається, питання, яке перевіряє вашу впевненість у собі крім технічних знань.

До речі, якщо ви відповіли

Обидва фрагменти коду однаково швидкі, тому що обидва займають нескінченний час для завершення

сказав би інтерв'юер

Але while (1)можна робити більше ітерацій за секунду; ви можете пояснити, чому? (це нісенітниця; ще раз перевірити свою впевненість)

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


Ось приклад коду, згенерованого компілятором у моїй системі (MS Visual Studio 2012), з оптимізаціями вимкнено:

yyy:
    xor eax, eax
    cmp eax, 1     (or 2, depending on your code)
    je xxx
    jmp yyy
xxx:
    ...

З увімкненими оптимізаціями:

xxx:
    jmp xxx

Тож згенерований код точно такий же, принаймні, з оптимізуючим компілятором.


28
Цей код справді те, що компілятор виводить у моїй системі. Я не придумував.
anatolyg

10
icepack "Операнд на той час булевого типу" - цілком нісенітниця. Ви інтерв'юер? Я пропоную вам ознайомитись з мовою C та її стандартом перед тим, як заявляти такі твердження.
Джим Балтер

29
"Я не придумував". - Будь ласка, не звертайте жодної уваги на крижинку, яка говорить дурниці. C не має булевого типу (у нього є _Bool в stdbool.h, але це не те саме, і семантика whileдавно передує йому), і операнд whileне булів, ні _Bool, ні будь-який інший специфічний тип. Операнд whileможе бути будь-яким виразом ... час перерви на 0 і продовжується на не-0.
Джим Балтер

36
"Обидва фрагменти коду однаково швидкі, тому що обом потрібно нескінченний час для завершення", змусив мене подумати про щось цікаве. Єдиним способом припинити нескінченний цикл було б трохи перевернути заряджену частинку або збій апаратури: зміну оператора з while (00000001) {}на while (00000000) {}. Чим більше істинних бітів у вас менше шансів, що значення перетвориться на хибне. На жаль, 2 також має лише один справжній біт. 3, однак, буде працювати значно довше. Це стосується лише компілятора, який не завжди оптимізує це (VC ++).
Джонатан Дікінсон

8
@ mr5 nope. Для того, щоб трохи перевернути насправді щось подібне, ви говорите про терміни виконання у десятки тисяч років. Просто мислений експеримент. Якщо ви приїхали з безсмертної раси, ви можете скористатись -1, щоб запобігти пошкодженню не впливати на вашу програму.
Джонатан Дікінсон,

60

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

1 = 00000001
2 = 00000010

Якщо "дорівнює нулю?" алгоритм починається з правого боку числа і повинен перевіряти кожен біт, поки він не досягне нульового біта, while(1) { }цикл повинен буде перевірити вдвічі більше бітів за ітерацію, ніж while(2) { }цикл.

Для цього потрібна дуже неправильна ментальна модель того, як працюють комп'ютери, але у неї є своя внутрішня логіка. Один із способів перевірки - запитати, while(-1) { }чи while(3) { }буде однаково швидкий, чи while(32) { }буде ще повільніше .


39
Я припускав, що неправильне розуміння інтерв'юера буде більше схожим на "2 - це int, який потрібно перетворити на булевий, щоб використовувати його в умовному виразі, тоді як 1 вже булевий".
Рассел Борогов

7
І якщо алгоритм порівняння починається зліва, це навпаки.
Paŭlo Ebermann

1
+1, саме так я і подумав. Ви могли б ідеально уявити, хтось вважає, що принципово cmpалгоритм процесора - це лінійна бітова перевірка з ранньою циклом виходу за першою відмінністю.
v.oddou

1
@PeterCordes "Я ніколи не чув, щоб хто-небудь (крім вас) заперечував, що -O0вихід gcc або clang недостатній буквально". Я ніколи такого не говорив, і я взагалі не мав на увазі gcc або clang. Ти, мабуть, мене неправильно читаєш. Я просто не вважаю, що те, що виробляє MSVC, є смішним.
blubberdiblub

1
@PeterCordes Я помилявся, в MSVC точка розриву на точку while(1)не розривається перед циклом, а на виразі. Але він все одно руйнується всередині циклу при оптимізації виразу вдалині. Візьміть цей код з великою кількістю перерв. У неоптимізованому режимі налагодження ви отримуєте це . Під час оптимізації багато точок розриву падають разом або навіть переливаються на наступну функцію (IDE показує результати точки прориву під час налагодження) - відповідну розбирання .
blubberdiblub

32

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

Вони ввели вас у конфлікт. Якщо це правда, вони розумні і питання було добре. У деяких галузях, таких як банківська справа, розміщення вашої проблеми в стеці переповнення може стати причиною відмови.

Але я, звичайно, не знаю, я просто пропоную один варіант.


Це справді чудово, але в той час, як (2) проти в той час, як (1), очевидно, взято з комірок з ділбертом. ЇЇ НЕ МОЖНА придумати хтось із розумом (як це хтось придумає, поки (2) як можливу річ написати?). Якби ваша гіпотеза була правдивою, напевно ви поставили б проблему, настільки унікальну, що можете її вирішити. Як "у той час, як (0xf00b442) повільніше, ніж в той час, як у той час, як (1)", як банк знайшов б питання запитуваного в іншому випадку? ви гадаєте, що вони є АНБ і мають доступ до клавіш?
v.oddou

25

Я думаю, що підказку можна знайти в "запитанні старшого керівника". Ця людина, очевидно, перестала програмувати, коли стала менеджером, і тоді йому знадобилося кілька років, щоб стати старшим менеджером. Ніколи не втрачав інтересу до програмування, але ніколи не писав рядки з тих днів. Тож його посилання - це не "якийсь пристойний компілятор там", як згадують деякі відповіді, а "упорядник, з яким ця людина працювала 20-30 років тому".

У той час програмісти витрачали чималий відсоток свого часу на тестування різних методів для того, щоб зробити свій код швидшим та ефективнішим, оскільки час ЦП "центрального мінікомп'ютера" був таким цінним. Як і люди, що писали компілятори. Я здогадуюсь, що єдиний компілятор, який його компанія зробив доступним на той час, був оптимізований на основі "часто зустрічаються тверджень, які можна оптимізувати", і, скориставшись деяким ярликом, стикався з деяким часом (1) і оцінював усе інше, включаючи деякий час (2). Маючи такий досвід, можна пояснити свою позицію та впевненість у ній.

Найкращим підходом до прийняття на роботу є, мабуть, такий, який дозволяє старшому керівнику захопитися і викладати 2-3 хвилини на тему "старі добрі програми програмування", перш ніж ВИ плавно ведуть його до наступної теми інтерв'ю. (Тут важливий гарний час - занадто швидкий, і ви перериваєте історію - занадто повільно, і вас позначають як когось із недостатньою увагою). Скажіть йому в кінці інтерв'ю, що вам буде дуже цікаво дізнатися більше про цю тему.


19

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


14
@GKFX Якщо ви дали свою відповідь, і вони говорять вам, що ви помиляєтесь, немає жодної причини, щоб ви не могли попросити їх пояснити чому. Якщо Анатолій правильно, і це перевірка вашої впевненості в собі, то вам слід пояснити, чому ви відповіли так, як ви це зробили, і запитати їх те саме.
P.Turpie

Я мав на увазі як перше, що ви їм скажете. Це не може бути "Чому х швидше?" "Не знаю; чому х швидше?". Очевидно, відповівши належним чином, можна потім запитати.
GKFX

18

Заради цього питання, я повинен додати, що я пам’ятаю, що Дуг Гвін з комітету C писав, що деякі ранні компілятори C без проходу оптимізатора генерують тест у збірці для while(1)(порівняння з for(;;)яким цього не було б).

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

  • без оптимізатора пройти компілятор генерує тест і для, while(1)і дляwhile(2)
  • за допомогою оптимізатора пройти компілятор доручає оптимізувати (з безумовним стрибком) все, while(1)тому що вони розглядаються як ідіоматичні. Це не дозволить while(2)пройти тест, а отже, змінить продуктивність між ними.

Я, звичайно, додам інтерв'юеру, що не врахування while(1)та while(2)однакова конструкція є ознакою низької якості оптимізації, оскільки це еквівалентні конструкції.


10

Ще одне питання про таке, щоб переконатися, чи набралися ви сміливості сказати своєму менеджеру, що він / вона помиляється! І як м'яко ви можете це спілкувати.

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


8

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

Моя відповідь була б; це не так важливо, я скоріше зосереджуюсь на бізнес-проблемі, яку ми вирішуємо. Зрештою, саме за це я буду платити.

Більше того, я б став на вибір, while(1) {}тому що це частіше, а іншим товаришам по команді не потрібно було б витрачати час, щоб з'ясувати, чому хтось піде на більшу кількість, ніж 1.

Тепер підемо написати якийсь код. ;-)


1
Якщо вам не заплатять за оптимізацію коду в реальному часі, для якого вам потрібно поголити лише 1 або 2 мілісекунди, щоб відповідати його вимогам часу роботи. Звичайно, це робота для оптимізатора, дехто би сказав - це якщо у вас є оптимізатор для вашої архітектури.
Neowizard

6

Якщо ви переживаєте за оптимізацію, вам варто скористатися

for (;;)

тому що тестів немає. (цинічний режим)


2
Ось чому це розумно використовувати while(2), це залишає місце для оптимізації, після чого ви просто переходите до того, for (;;)коли будете готові до підвищення продуктивності.
Груо

5

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

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


3

Раніше я програмував код C і Assembly, коли ця дурниця могла змінити щось. Коли це все змінилося, ми написали це в Асамблеї.

Якби мені задали це запитання, я повторив би відому цитату Дональда Кнута 1974 року про передчасну оптимізацію і пішов, якби інтерв'юер не сміявся і рухався далі.


1
Зважаючи на те, що він також сказав: "У встановлених інженерних дисциплінах вдосконалення на 12%, яке легко отримати, ніколи не вважається маргінальним, і я вважаю, що однакова точка зору повинна переважати в інженерії програмного забезпечення", я вважаю, що ви ходите, виправдані.
Аліса

3

Можливо, інтерв'юер поставив таке німе запитання навмисно і хотів, щоб ви зробили 3 бали:

  1. Основні міркування. Обидві петлі нескінченні, про продуктивність важко говорити.
  2. Знання про рівні оптимізації. Він хотів почути від вас, якщо ви дозволите компілятору зробити будь-яку оптимізацію для вас, це оптимізувало б умову, особливо якщо блок не був порожнім.
  3. Знання про мікропроцесорну архітектуру. Більшість архітектур мають спеціальну інструкцію процесора для порівняння з 0 (хоча і не обов'язково швидше).

2

Ось проблема: Якщо ви насправді пишете програму і вимірюєте її швидкість, швидкість обох циклів може бути різною! Для трохи розумного порівняння:

unsigned long i = 0;
while (1) { if (++i == 1000000000) break; }

unsigned long i = 0;
while (2) { if (++i == 1000000000) break; }

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

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


1
Міркування "рядків кеша" працюватимуть лише на чіпі, який має надзвичайно малий кеш.
гострий зуб

10
Це поруч. Питання щодо швидкості вважають "всі інші рівні".
Джим Балтер

6
@JimBalter: Це не питання швидкості, а питання аргументу з інтерв'юером. І можливість того, що робити фактичний тест, може довести інтерв'юера «правильно» - з причин, які не мають нічого спільного з його аргументом, але є чистим збігом обставин. Що поставило б вас у незручну ситуацію, якби ви спробували довести, що він помилявся.
gnasher729

1
@ gnasher729 Усі логічні аргументи в стороні, випадковість збігу, про яку ви говорите, варто переглянути. Але все ж сліпо вірити, що такий випадок існує не тільки наївно, але й дурно. Поки ви не поясните, що, як і чому це сталося, ця відповідь марна.
користувач568109

4
@ gnasher729 "Це не питання швидкості" - я не буду обговорювати людей, які використовують нечесні риторичні прийоми.
Джим Балтер

2

Вони обидва рівні - однакові.

Відповідно до специфікацій, все, що не дорівнює 0, вважається істинним, тому навіть без оптимізації, а хороший компілятор не генерує жодного коду на час (1) або поки (2). Компілятор генерував би просту перевірку != 0.


@djechlin - адже вони обидва беруть 1 цикл процесора.
UncleKing

Постійна компілятора згортає її, тому вона навіть не оцінюється під час виконання.
PaulHK

2

Судячи з кількості часу та зусиль, які люди витратили на тестування, доведення та відповідь на це прямо пряме запитання, я скажу, що обидва були зроблені дуже повільно, задаючи питання.

І так витратити на це ще більше часу ...

"while (2)" є смішним, тому що,

"while (1)" і "while (true)" історично використовуються для створення нескінченного циклу, який очікує, що "перерва" буде викликана на певному етапі всередині циклу на основі умови, яка неодмінно відбудеться.

"1" просто існує для того, щоб завжди оцінювати справжнє, а тому говорити "в той час як (2)" приблизно так само нерозумно, як сказати "в той час, як (1 + 1 == 2)", що також оцінить як істинне.

А якщо ви хочете бути абсолютно нерозумним, просто використовуйте: -

while (1 + 5 - 2 - (1 * 3) == 0.5 - 4 + ((9 * 2) / 4)) {
    if (succeed())
        break;
}

Мені подобається думати, що ваш кодер зробив помилку, яка не вплинула на виконання коду, але якщо він навмисно використав "2" просто для дивацтва, тоді звільніть його, перш ніж він поставить дивні ш! Т усі через ваш код, що ускладнить читати і працювати з.


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

@Palec: Дякую за коментар. Я виявив, що той самий хлопець редагував багато моїх публікацій, здебільшого лише для того, щоб зняти знак. Тож я зрозумів, що він просто репутація троля, а отже, і його репутація. Чи настільки сильно виснажили онлайн-форуми мову - і загальні ввічливості - що ми більше не підписуємо свої твори? можливо, я трохи стара школа, але просто здається грубим опускати привітання та прощання. Ми використовуємо додатки, але ми все ще люди.
екернер

1
На Stack біржі , вітань, слогани, спасибі і т.д. не вітаються . Те саме згадується в довідковому центрі , конкретно при очікуваній поведінці . Жодна частина Stack Exchange , навіть не переповнення стека , не є форумом - це сайти з питань запитання. Редагування - одна з відмінностей. Також коментарі повинні слугувати лише для надання відгуку про публікацію, а не для обговорення ( Stack Overflow Chat ). І є багато інших способів, які Q & A відрізняються від форуму. Детальніше про це у довідковому центрі та про Meta Stack Overflow .
Палець

Зауважте, що якщо у вас більше 2000 повторів (привілей редагування), ви більше не отримуєте повторного редагування. Якщо ви сумніваєтесь, чому до вашої публікації було застосовано та чи не редагування, з яким ви не погоджуєтесь, або ви виявляєте чиєсь недобре поведінку, запитайте про переповнення Meta Stack . Якщо редактор зробив неправильно, їх могли повідомити мод (можливо, вони не усвідомлювали) або навіть якось покарати (якщо поведінка була навмисно злісною). В іншому випадку ви отримуєте вказівники на пояснення, чому редагування / поведінка є правильним. Зайве відкатування загрожує історією перегляду і може ввести вас у відкатну війну. Я відкочуюсь лише тоді, коли дійсно впевнений, що редакція невірна.
Палець

Нарешті про підписання, привітання,…: Можливо, здається, грубо не включати ці формальності, але це збільшує співвідношення сигнал / шум і інформація не втрачається. Ви можете вибрати будь-який псевдонім, який хочете, тобто ваш підпис. У більшості місць у вас є і ваш аватар.
Палець

1

Це залежить від компілятора.

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

У реальних випадках це завжди буде однаково швидко, але можна було б уявити конкретний компілятор та певну систему, коли це буде оцінено по-різному.

Я маю на увазі: це насправді не питання, пов'язане з мовою (С).


0

Оскільки люди, які хочуть відповісти на це запитання, хочуть отримати найшвидший цикл, я б відповів, що обидва однаково складаються в один і той же код складання, як зазначено в інших відповідях. Тим не менш, ви можете запропонувати інтерв'юеру скористатися «розкручуванням циклу»; a do {} while цикл замість циклу while.

Обережно: Вам потрібно забезпечити, щоб цикл принаймні завжди працював один раз .

Петля повинна мати стан розриву всередині.

Також для такого типу циклу я особисто вважаю за краще використовувати do {} while (42), оскільки будь-яке ціле число, крім 0, буде виконувати цю роботу.


Чому він запропонував би зробити цикл {}? Тоді як (1) (або поки (2)) робить те саме.
Кацунамі

виконайте, поки знімає один зайвий стрибок, так що кращі показники. Звичайно, у випадку, коли вам відомо, що цикл буде виконаний хоча б один раз
Антонін ГАВРЕЛ

0

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

Хоча переосмислення ключових слів C як макросів технічно мало б невизначене поведінку, це єдиний спосіб, коли я можу думати про те, щоб взагалі швидко зробити будь-який фрагмент коду: ви можете додати цей рядок над двома фрагментами:

#define while(x) sleep(x);

це дійсно зробить while(1)удвічі швидше (або вдвічі повільніше), ніж while(2).


@MaxB: дійсно макро-трюк повинен бути ще більш спотвореним. Я думаю, що нова версія повинна працювати, хоча це не гарантується стандартом C.
chqrlie

-4

Єдина причина, з якої я можу подумати, чому це while(2)буде повільніше, це:

  1. Код оптимізує цикл для

    cmp eax, 2

  2. Коли відбувається віднімання, ви по суті віднімаєте

    а. 00000000 - 00000010 cmp eax, 2

    замість

    б. 00000000 - 00000001 cmp eax, 1

cmpвстановлює лише прапори і не встановлює результат. Таким чином , по найменш значущих бітів ми знаємо , якщо ми повинні запозичувати чи ні з б . У той час як з ви повинні виконати два вирахування , перш ніж отримати позику.


15
CPM буде приймати цикл 1 процесор незалежно в обох випадках.
UncleKing

8
Цей код був би невірним. Правильний код буде завантажувати або 2, або 1, щоб зареєструвати eax, а потім порівняти eax з 0.
gnasher729
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.