В двух словах (tl; dr):
Я интерпретирую комментарий в коде OP немного по-другому, я думаю, что «лучший код», который, по их утверждениям, наблюдался, был вызван переносом фактической работы в «условие» цикла. Однако я полностью согласен с тем, что он очень специфичен для компилятора и что проведенное ими сравнение, хотя и позволяет получить немного другой код, в основном бессмысленно и, вероятно, устарело, как я покажу ниже.
Детали:
Трудно сказать , что оригинальный автор имел в виду его комментарий по поводу этого do {} while
производящего лучшего кода, но я хотел бы порассуждать в другом направлении , чем то , что был воспитан здесь , - мы считаем , что разница между do {} while
и while {}
петли довольно тонкий (один меньше ветвь , как Мистический сказал), но в этом коде есть что-то еще «смешнее», и это помещает всю работу в это сумасшедшее состояние и сохраняет внутреннюю часть пустой ( do {}
).
Я пробовал следующий код на gcc 4.8.1 (-O3), и он дает интересную разницу:
#include "stdio.h"
int main (){
char buf[10];
char *str = "hello";
char *src = str, *dst = buf;
char res;
do {
res = (*dst++ = *src++);
} while (res);
printf ("%s\n", buf);
src = str;
dst = buf;
do {
} while (*dst++ = *src++);
printf ("%s\n", buf);
return 0;
}
После компиляции -
00000000004003f0 <main>:
...
; loop 1
400400: 48 89 ce mov %rcx,%rsi
400403: 48 83 c0 01 add $0x1,%rax
400407: 0f b6 50 ff movzbl 0xffffffffffffffff(%rax),%edx
40040b: 48 8d 4e 01 lea 0x1(%rsi),%rcx
40040f: 84 d2 test %dl,%dl
400411: 88 16 mov %dl,(%rsi)
400413: 75 eb jne 400400 <main+0x10>
...
;loop 2
400430: 48 83 c0 01 add $0x1,%rax
400434: 0f b6 48 ff movzbl 0xffffffffffffffff(%rax),%ecx
400438: 48 83 c2 01 add $0x1,%rdx
40043c: 84 c9 test %cl,%cl
40043e: 88 4a ff mov %cl,0xffffffffffffffff(%rdx)
400441: 75 ed jne 400430 <main+0x40>
...
Таким образом, первый цикл выполняет 7 инструкций, а второй - 6, хотя они должны выполнять ту же работу. Я не могу точно сказать, стоит ли за этим какая-то хитрость компилятора, возможно, нет, и это просто совпадение, но я не проверял, как он взаимодействует с другими параметрами компилятора, которые может использовать этот проект.
В clang 3.3 (-O3), с другой стороны, оба цикла генерируют этот код из 5 инструкций:
400520: 8a 88 a0 06 40 00 mov 0x4006a0(%rax),%cl
400526: 88 4c 04 10 mov %cl,0x10(%rsp,%rax,1)
40052a: 48 ff c0 inc %rax
40052d: 48 83 f8 05 cmp $0x5,%rax
400531: 75 ed jne 400520 <main+0x20>
Это просто показывает, что компиляторы совершенно разные и развиваются гораздо быстрее, чем некоторые программисты могли ожидать несколько лет назад. Это также означает, что этот комментарий довольно бессмысленный и, вероятно, существует потому, что никто никогда не проверял, имеет ли он смысл.
Итог - если вы хотите оптимизировать код до наилучшего из возможных (и знаете, как он должен выглядеть), сделайте это прямо в сборке и вырежьте «посредника» (компилятор) из уравнения, но примите во внимание, что более новый компиляторы и более новое HW могут сделать эту оптимизацию устаревшей. В большинстве случаев гораздо лучше просто позволить компилятору делать этот уровень работы за вас и сосредоточиться на оптимизации больших вещей.
Еще один момент, на который следует обратить внимание - количество команд (при условии, что это то, что было после исходного кода OP), ни в коем случае не является хорошим показателем эффективности кода. Не все инструкции были созданы равными, и некоторые из них (например, простые переходы от reg-to-reg) действительно дешевы, так как они оптимизируются процессором. Другая оптимизация может повредить внутренней оптимизации ЦП, поэтому в конечном итоге учитываются только правильные тесты.