Проверка рекомендации "не берите на себя работу компилятора" из книги "Главный вопрос программирования, рефакторинга и всего такого".
Примечание. Статья впервые была опубликована на русском языке на сайте livejournal.com. Статья и её перевод размещаются на нашем сайте с согласия автора.
В блоге PVS-Studio можно найти много примеров ошибок, совершаемых программистами, и много рекомендаций о том, как их избежать (http://www.viva64.com/ru/b/0391/).
Первый совет в этом документе: не берите на себя работу компилятора. Не оптимизируйте вручную то, что и так будет оптимизировано. В качестве примера приведён код из MySQL, в котором программист вручную развернул цикл, допустив при этом ошибку. Далее в статье написано, что если написать эту же функцию через цикл, то компилятор, скорее всего, её развернёт.Давайте проверим это с помощью онлайн-компилятора. Я скопировал исходники функции из статьи, лишь немного подправив их так, чтобы они компилировались без дополнительных файлов.Компиляция выполнялась компилятором clang 3.9 для таргета arm с уровнем оптимизации -O2 (при котором компилятор разворачивает циклы).
Итак, оригинальный исходник:
int rr_cmp(unsigned char *a, unsigned char *b)
{
if (a[0] != b[0])
return (int) a[0] - (int) b[0];
if (a[1] != b[1])
return (int) a[1] - (int) b[1];
if (a[2] != b[2])
return (int) a[2] - (int) b[2];
if (a[3] != b[3])
return (int) a[3] - (int) b[3];
if (a[4] != b[4])
return (int) a[4] - (int) b[4];
if (a[5] != b[5])
return (int) a[5] - (int) b[5];
if (a[6] != b[6])
return (int) a[6] - (int) b[6];
return (int) a[7] - (int) b[7];
}
Результат для arm:
rr_cmp(unsigned char*, unsigned char*):
@ @rr_cmp(unsigned char*, unsigned char*)
ldrb r2, [r1]
ldrb r3, [r0]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #1]
ldrb r3, [r0, #1]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #2]
ldrb r3, [r0, #2]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #3]
ldrb r3, [r0, #3]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #4]
ldrb r3, [r0, #4]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #5]
ldrb r3, [r0, #5]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #6]
ldrb r3, [r0, #6]
cmp r3, r2
ldrbeq r1, [r1, #7]
ldrbeq r0, [r0, #7]
subeq r0, r0, r1
bxeq lr
.LBB0_7:
sub r0, r3, r2
bx lr
Исходник, предлагаемый автором из PVS:
int rr_cmp(unsigned char *a,unsigned char *b)
{
for (int i = 0; i < 7; ++i)
{
if (a[i] != b[i])
return a[i] - b[i];
}
return a[7] - b[7];
}
Результат:
rr_cmp(unsigned char*, unsigned char*):
@ @rr_cmp(unsigned char*, unsigned char*)
ldrb r2, [r1]
ldrb r3, [r0]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #1]
ldrb r3, [r0, #1]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #2]
ldrb r3, [r0, #2]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #3]
ldrb r3, [r0, #3]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #4]
ldrb r3, [r0, #4]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #5]
ldrb r3, [r0, #5]
cmp r3, r2
bne .LBB0_7
ldrb r2, [r1, #6]
ldrb r3, [r0, #6]
cmp r3, r2
ldrbeq r1, [r1, #7]
ldrbeq r0, [r0, #7]
subeq r0, r0, r1
bxeq lr
.LBB0_7:
sub r0, r3, r2
bx lr
Как говорится, найдите 10 отличий.
Всё идентично.
Однако, для x86-64 результат будет несколько иной, как ни странно. Но это уже другая история.