2016-04-02 3 views
2

Рассмотрим следующий код 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?

+0

Потенциал неопределенного поведения. 'long double' не требуется иметь 10 байтов. Что вы хотите сделать с этим кодом? Он выглядит запутанным и как решение в поисках проблемы. – Olaf

+0

@Olaf Как я уже сказал, я предполагаю этот размер из-за выбранной цели (Linux x86). – Ruslan

+0

Я проголосую. Как я объяснил ниже, речь идет об оптимизации, а не о последовательности кода. И компилятор действительно неспособен оптимизировать его. Или просто выход - действительно лучшая оптимизация, даже если уродливая, чтобы видеть. –

ответ

8

Во-первых, он не может сделать эту оптимизацию, потому что вы полностью запутали смысл своего кода, перегрузив его чрезмерным количеством переинтерпретации памяти. Такой код справедливо заставляет компилятор реагировать на «Я не знаю, что на Земле это, но если это то, что вы хотите, вот что вы получите». Почему вы ожидаете, что компилятор даже потрудится преобразовать некоторую интерпретацию памяти в другой тип переинтерпретации памяти (!), Для меня совершенно непонятно.

Во-вторых, его можно, вероятно, сделать, чтобы сделать это теоретически, но, вероятно, он не очень высок в списке его приоритетов. Помните, что оптимизацию кода обычно выполняют с помощью шаблона , соответствующего алгоритму, а не каким-то A.I. И это не один из шаблонов, которые он распознает.

В большинстве случаев ваше руководство пытается выполнить низкоуровневую оптимизацию кода, что приведет к сбою усилий компилятора, чтобы сделать то же самое. Если вы хотите оптимизировать его самостоятельно, то идите полностью. Не ожидайте, что сможете начать, а затем передать его компилятору, чтобы завершить работу за вас.

Сравнение двух величин long doublex и y можно сделать очень легко: x == y. Если вам требуется сравнение бит-бит памяти, вы, вероятно, упростите работу компилятора, просто используя memcmp в компиляторе, который по своей сути знает, что такое memcmp (встроенная, внутренняя функция).

+0

Сравнение может быть выполнено очень легко, если вы хотите сравнение _floating-point_. Не так просто, если вы хотите сравнить бит для бит. – Ruslan

+0

Я могу понять вашу точку зрения, но вопрос о OP заключается не в согласованности кода. Скорее всего, именно по этой причине воздерживаться от компилятора от оптимизации кода, что вполне законно, даже если оно может казаться уродливым. Более того, точка зрения Руслана о сравнении битов может иметь некоторый смысл (простое сравнение значений float всегда является проблемой). Я думаю, что ответ заключается в том, что разворачивание цикла 'for' является самым быстрым кодом. В любом случае PellesC не разворачивает цикл и не создает код, очень близкий к запрошенному. –

+0

Хорошая мысль о 'memcmp'. Даже если суть вопроса остается странной оптимизацией. –

Смежные вопросы