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

от автора

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

while (true) { ... } 

и

for (;;) { ... } 

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

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

while.c:

#include <stdio.h>  int main (int argc, char* argv[]) {     while(1){        printf("1\n");     } }  

for.c:

#include <stdio.h>  int main (int argc, char* argv[]) {     for(;;){         printf("1\n");     } } 

Собрал их:

$ 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)”

ссылка на оригинал статьи http://habrahabr.ru/post/198588/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *