Рассмотрим следующий код C (предполагается, что 80-битный long double
) (заметьте, я не знаю memcmp
, это просто эксперимент):Почему компилятор не может оптимизировать этот код?
enum { sizeOfFloat80=10 }; // NOTE: sizeof(long double) != sizeOfFloat80
_Bool sameBits1(long double x, long double y)
{
for(int i=0;i<sizeOfFloat80;++i)
if(((char*)&x)[i]!=((char*)&y)[i])
return 0;
return 1;
}
Все компиляторы я проверил (НКУ, лязг, МКИ по НКУ. godbolt.org) генерировать подобный код, вот пример для GCC с вариантами -O3 -std=c11 -fomit-frame-pointer -m32
:
sameBits1:
movzx eax, BYTE PTR [esp+16]
cmp BYTE PTR [esp+4], al
jne .L11
movzx eax, BYTE PTR [esp+17]
cmp BYTE PTR [esp+5], al
jne .L11
movzx eax, BYTE PTR [esp+18]
cmp BYTE PTR [esp+6], al
jne .L11
movzx eax, BYTE PTR [esp+19]
cmp BYTE PTR [esp+7], al
jne .L11
movzx eax, BYTE PTR [esp+20]
cmp BYTE PTR [esp+8], al
jne .L11
movzx eax, BYTE PTR [esp+21]
cmp BYTE PTR [esp+9], al
jne .L11
movzx eax, BYTE PTR [esp+22]
cmp BYTE PTR [esp+10], al
jne .L11
movzx eax, BYTE PTR [esp+23]
cmp BYTE PTR [esp+11], al
jne .L11
movzx eax, BYTE PTR [esp+24]
cmp BYTE PTR [esp+12], al
jne .L11
movzx eax, BYTE PTR [esp+25]
cmp BYTE PTR [esp+13], al
sete al
ret
.L11:
xor eax, eax
ret
Это выглядит некрасиво, имеет филиал на каждый байт и в самом деле, кажется, не были оптимизированы на всех (но, по крайней мере, цикл развернут). Легко видеть, однако, что это может быть оптимизирован для кода эквивалентен следующему (и в целом для больших данных использовать большие шаги):
#include <string.h>
_Bool sameBits2(long double x, long double y)
{
long long X=0; memcpy(&X,&x,sizeof x);
long long Y=0; memcpy(&Y,&y,sizeof y);
short Xhi=0; memcpy(&Xhi,sizeof x+(char*)&x,sizeof Xhi);
short Yhi=0; memcpy(&Yhi,sizeof y+(char*)&y,sizeof Yhi);
return X==Y && Xhi==Yhi;
}
И этот код теперь получает гораздо приятнее результат компиляции:
sameBits2:
sub esp, 20
mov edx, DWORD PTR [esp+36]
mov eax, DWORD PTR [esp+40]
xor edx, DWORD PTR [esp+24]
xor eax, DWORD PTR [esp+28]
or edx, eax
movzx eax, WORD PTR [esp+48]
sete dl
cmp WORD PTR [esp+36], ax
sete al
add esp, 20
and eax, edx
ret
Итак, мой вопрос: почему ни один из трех компиляторов не может сделать эту оптимизацию? Это что-то очень необычное, чтобы увидеть в коде C?
Потенциал неопределенного поведения. 'long double' не требуется иметь 10 байтов. Что вы хотите сделать с этим кодом? Он выглядит запутанным и как решение в поисках проблемы. – Olaf
@Olaf Как я уже сказал, я предполагаю этот размер из-за выбранной цели (Linux x86). – Ruslan
Я проголосую. Как я объяснил ниже, речь идет об оптимизации, а не о последовательности кода. И компилятор действительно неспособен оптимизировать его. Или просто выход - действительно лучшая оптимизация, даже если уродливая, чтобы видеть. –