Что быстрее while (true) или for (;;)?

в 15:39, , рубрики: c++, for, gcc, objdump, while, метки: , , , , ,

В сырцах разных авторов видел я разные варианты вечного цикла. Чаще всего мне встречались следующие:

while (true) {
...
}

и

for (;;) {
...
}

Поскольку каждый защищал “свой вечный цикл” как родного, я решил разобраться. Кто же пишет более оптимальный код.

Я написал 2 исходника:

while.c:

#include <stdio.h>

int main (int argc, char* argv[])
{
    while(1){
       printf("1n");
    }
}

for.c:

#include <stdio.h>

int main (int argc, char* argv[])
{
    for(;;){
        printf("1n");
    }
}

Собрал их:

$ gcc -O3 while.c -o while.o3
$ gcc -O2 while.c -o while.o2
$ gcc -O1 while.c -o while.o1
$ gcc -O3 for.c -o for.o3
$ gcc -O2 for.c -o for.o2
$ gcc -O1 for.c -o for.o1

И дезассемблировал. Кому лень читать ассемблерные листниги — можете прокрутить страницу вниз. Собственно листинги:


$ objdump -d ./while.o3
...
0000000000400430 <main>:
 400430:       48 83 ec 08             sub    $0x8,%rsp
 400434:       0f 1f 40 00             nopl   0x0(%rax)
 400438:       bf d4 05 40 00          mov    $0x4005d4,%edi
 40043d:       e8 be ff ff ff          callq  400400 <puts@plt>
 400442:       eb f4                   jmp    400438 <main+0x8>
...
$ objdump -d ./while.o2
...
0000000000400430 <main>:
 400430:       48 83 ec 08             sub    $0x8,%rsp
 400434:       0f 1f 40 00             nopl   0x0(%rax)
 400438:       bf d4 05 40 00          mov    $0x4005d4,%edi
 40043d:       e8 be ff ff ff          callq  400400 <puts@plt>
 400442:       eb f4                   jmp    400438 <main+0x8>
...
$ objdump -d ./while.o1
...
000000000040051c <main>:
 40051c:       48 83 ec 08             sub    $0x8,%rsp
 400520:       bf d4 05 40 00          mov    $0x4005d4,%edi
 400525:       e8 d6 fe ff ff          callq  400400 <puts@plt>
 40052a:       eb f4                   jmp    400520 <main+0x4>
...
$ objdump -d ./for.o1
...
000000000040051c <main>:
 40051c:       48 83 ec 08             sub    $0x8,%rsp
 400520:       bf d4 05 40 00          mov    $0x4005d4,%edi
 400525:       e8 d6 fe ff ff          callq  400400 <puts@plt>
 40052a:       eb f4                   jmp    400520 <main+0x4>
...
$ objdump -d ./for.o2
...
0000000000400430 <main>:
 400430:       48 83 ec 08             sub    $0x8,%rsp
 400434:       0f 1f 40 00             nopl   0x0(%rax)
 400438:       bf d4 05 40 00          mov    $0x4005d4,%edi
 40043d:       e8 be ff ff ff          callq  400400 <puts@plt>
 400442:       eb f4                   jmp    400438 <main+0x8>
...
$ objdump -d ./for.o3
0000000000400430 <main>:
 400430:       48 83 ec 08             sub    $0x8,%rsp
 400434:       0f 1f 40 00             nopl   0x0(%rax)
 400438:       bf d4 05 40 00          mov    $0x4005d4,%edi
 40043d:       e8 be ff ff ff          callq  400400 <puts@plt>
 400442:       eb f4                   jmp    400438 <main+0x8>

Разбираем на пальцах

Различные оптимизации не повлияли на реализацию цикла while (true) — он всегда выполнял 3 команды: mov, callq и jmp. Так же оптимизации не повлияли на реализацию for — он тоже всегда был из 3х команд: mov, callq, jmp. Между собой mov, callq и jmp ничем не отличались. Длинна команд в байтах во всех 6и случаях неизменна.

Есть только небольшая разница между реализациями -O1 и -O2/-O3 jmp выполнялся на main+4 а не на main+8, но с учетом того, что это статичный адрес (как видно из asm-кода) оно тоже не несет разницы в производительности… Хотя… а вдруг страницы памяти разные, ведь на сколько я знаю для телодвижений между разными страницами памяти в x86 (и amd64) требуются дополнительные усилия проца!

Узнаем:
400438/4096 = 97,763183594
400520/4096 = 97,783203125

Пронесло. Страница памяти одна. Да это 97 страница Виртуальной памяти Виртуального адресного пространства процесса. Но именно она нам и нужна.

Итог

while (true) и for (;;) идентичны по производительности между собой и с любыми оптимизациями -Ox. Так что если Вас спросят кто из них быстрее — смело говорите что “for (;;)” — 8 символов написать быстрее, чем “while (true)” — 12 символов.

Для тех, кто не верит что без -Ox будет тоже самое:

$ gcc while.c -o while.noO
$ objdump -d while.noO
...
 40052b:       bf e4 05 40 00          mov    $0x4005e4,%edi
 400530:       e8 cb fe ff ff          callq  400400 <puts@plt>
 400535:       eb f4                   jmp    40052b <main+0xf>
...
$ gcc for.c -o for.noO
$ objdump -d for.noO
...
 40052b:       bf e4 05 40 00          mov    $0x4005e4,%edi
 400530:       e8 cb fe ff ff          callq  400400 <puts@plt>
 400535:       eb f4                   jmp    40052b <main+0xf>
...

P.S. конечно все это будет правдой на компиляторе “gcc version 4.7.2 (Debian 4.7.2-5)”

Автор: piromanlynx

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js