Подтвердить что ты не робот

Float to double conversion: почему так много инструкций?

Мне любопытно, может ли кто-нибудь пролить свет на это для меня. Я работаю над некоторыми числами преобразования данных, и у меня есть несколько функций, которые выполняют преобразования данных, которые я определяю с помощью двух макросов:

#define CONV_VIA_CAST(name, dtype, vtype)                               \
    static inline void name(void *data, void *view, size_t len) {       \
        vtype *vptr = (vtype*)view;                                     \
        dtype *dptr = (dtype*)data;                                     \
        for (size_t ii=0; ii < len/sizeof(vtype); ii++) {               \
            *vptr++ = (vtype)*dptr++;                                   \
        }                                                               \
    } 


#define CONV_VIA_FUNC(name, dtype, vtype, via)                          \
    static inline void name(void *data, void *view, size_t len) {       \
        vtype *vptr = (vtype*)view;                                     \
        dtype *dptr = (dtype*)data;                                     \
        for (size_t ii=0; ii < len/sizeof(vtype); ii++) {               \
            *vptr++ = (vtype)via(*dptr++);                              \
        }                                                               \
    } 

Когда я определяю преобразование float в int:

 CONV_VIA_FUNC(f_to_i, float, int16_t, lrintf); 

Я получаю хороший крошечный кусочек сборки с -O3 на:

   0x0000000000401fb0 <+0>:     shr    %rdx
   0x0000000000401fb3 <+3>:     je     0x401fd3 <f_to_i+35>
   0x0000000000401fb5 <+5>:     xor    %eax,%eax
   0x0000000000401fb7 <+7>:     nopw   0x0(%rax,%rax,1)
   0x0000000000401fc0 <+16>:    cvtss2si (%rdi,%rax,4),%rcx
   0x0000000000401fc6 <+22>:    mov    %cx,(%rsi,%rax,2)
   0x0000000000401fca <+26>:    add    $0x1,%rax
   0x0000000000401fce <+30>:    cmp    %rdx,%rax
   0x0000000000401fd1 <+33>:    jne    0x401fc0 <f_to_i+16>
   0x0000000000401fd3 <+35>:    repz retq 

Однако, когда я определяю функцию float- > double (или double- > float):

CONV_VIA_CAST(f_to_d, float,   double); 

Я получаю это чудовище:

   0x0000000000402040 <+0>:     mov    %rdx,%r8
   0x0000000000402043 <+3>:     shr    $0x3,%r8
   0x0000000000402047 <+7>:     test   %r8,%r8
   0x000000000040204a <+10>:    je     0x402106 <f_to_d+198>
   0x0000000000402050 <+16>:    shr    $0x5,%rdx
   0x0000000000402054 <+20>:    lea    0x0(,%rdx,4),%r9
   0x000000000040205c <+28>:    test   %r9,%r9
   0x000000000040205f <+31>:    je     0x402108 <f_to_d+200>
   0x0000000000402065 <+37>:    lea    (%rdi,%r8,4),%rax
   0x0000000000402069 <+41>:    cmp    $0xb,%r8
   0x000000000040206d <+45>:    lea    (%rsi,%r8,8),%r10
   0x0000000000402071 <+49>:    seta   %cl
   0x0000000000402074 <+52>:    cmp    %rax,%rsi
   0x0000000000402077 <+55>:    seta   %al
   0x000000000040207a <+58>:    cmp    %r10,%rdi
   0x000000000040207d <+61>:    seta   %r10b
   0x0000000000402081 <+65>:    or     %r10d,%eax
   0x0000000000402084 <+68>:    test   %al,%cl
   0x0000000000402086 <+70>:    je     0x402108 <f_to_d+200>
   0x000000000040208c <+76>:    xorps  %xmm3,%xmm3
   0x000000000040208f <+79>:    xor    %eax,%eax
   0x0000000000402091 <+81>:    xor    %ecx,%ecx
   0x0000000000402093 <+83>:    nopl   0x0(%rax,%rax,1)
   0x0000000000402098 <+88>:    movaps %xmm3,%xmm0
   0x000000000040209b <+91>:    add    $0x1,%rcx
   0x000000000040209f <+95>:    movlps (%rdi,%rax,1),%xmm0
   0x00000000004020a3 <+99>:    movhps 0x8(%rdi,%rax,1),%xmm0
   0x00000000004020a8 <+104>:   movhlps %xmm0,%xmm1
   0x00000000004020ab <+107>:   cvtps2pd %xmm0,%xmm2
   0x00000000004020ae <+110>:   cvtps2pd %xmm1,%xmm0
   0x00000000004020b1 <+113>:   movlpd %xmm2,(%rsi,%rax,2)
   0x00000000004020b6 <+118>:   movhpd %xmm2,0x8(%rsi,%rax,2)
   0x00000000004020bc <+124>:   movlpd %xmm0,0x10(%rsi,%rax,2)
   0x00000000004020c2 <+130>:   movhpd %xmm0,0x18(%rsi,%rax,2)
   0x00000000004020c8 <+136>:   add    $0x10,%rax
   0x00000000004020cc <+140>:   cmp    %rcx,%rdx
   0x00000000004020cf <+143>:   ja     0x402098 <f_to_d+88>
   0x00000000004020d1 <+145>:   cmp    %r9,%r8
   0x00000000004020d4 <+148>:   lea    (%rsi,%r9,8),%rsi
   0x00000000004020d8 <+152>:   lea    (%rdi,%r9,4),%rdi
   0x00000000004020dc <+156>:   je     0x40210d <f_to_d+205>
   0x00000000004020de <+158>:   mov    %r9,%rdx
   0x00000000004020e1 <+161>:   mov    %r9,%rax
   0x00000000004020e4 <+164>:   neg    %rdx
   0x00000000004020e7 <+167>:   lea    (%rsi,%rdx,8),%rcx
   0x00000000004020eb <+171>:   lea    (%rdi,%rdx,4),%rdx
   0x00000000004020ef <+175>:   nop
   0x00000000004020f0 <+176>:   movss  (%rdx,%rax,4),%xmm0
   0x00000000004020f5 <+181>:   cvtps2pd %xmm0,%xmm0
   0x00000000004020f8 <+184>:   movsd  %xmm0,(%rcx,%rax,8)
   0x00000000004020fd <+189>:   add    $0x1,%rax
   0x0000000000402101 <+193>:   cmp    %rax,%r8
   0x0000000000402104 <+196>:   ja     0x4020f0 <f_to_d+176>
   0x0000000000402106 <+198>:   repz retq 
   0x0000000000402108 <+200>:   xor    %r9d,%r9d
   0x000000000040210b <+203>:   jmp    0x4020de <f_to_d+158>
   0x000000000040210d <+205>:   nopl   (%rax)
   0x0000000000402110 <+208>:   retq   

Может ли кто-нибудь пролить свет на то, что происходит под капотом здесь для двойного преобразования float? И, возможно, как это могло бы быть написано, чтобы получить более эффективную сборку? Я использую gcc 4.6.3, если это имеет значение.

4b9b3361

Ответ 1

Здесь происходит несколько вещей, которые я могу видеть быстро (код немного длинный, время немного запоздало, и я не являюсь поклонником синтаксиса AT & T).

Во-первых, второй цикл был векторизован (но плохо, см. ниже). Это по своей сути вызывает раздувание кода - теперь ему приходится иметь дело с "хвостом", который короче, чем вектор и т.д.

Во-вторых, float to double является расширяющимся преобразованием. Это не имеет значения для скаляров, но с векторами, что означает, что вы не можете просто читать некоторые данные, преобразовывать их и записывать обратно - где-то вдоль строк вы получите вдвое большее количество байтов, и они должны быть обработаны с. (следовательно, movhlps %xmm0,%xmm1)

Фактический цикл охватывает только от 402098h до 4020cfh, ниже - "обработка хвоста", а выше это чудовище, которое проверяет, имеет ли он пропустить основной цикл полностью и некоторые вещи, которые я не совсем понял - было бы разумно, если бы это было для выравнивания, но я не вижу там никакого test rdi, 15, и ничего очевидного, что бы избавиться от неустановленного начала.

И, в-третьих, GCC хромает. Это не редкость. Кажется, что xmm3 каким-то образом задействован, чего нет, и, похоже, он забыл, что векторы могут быть загружены в память из одной части - тогда это может быть потому, что чудовище в начале действительно не было тест для выравнивания, и это его защита от неустановленных указателей. В любом случае, GCC сделал плохую работу здесь.

Ответ 2

То, что вы называете "monstrosity", на самом деле выглядит как автоматически векторизованный код. Что-то вроде 20-летнего исследования перешло к этой технике, прежде чем оно начало хорошо работать и быть полезным в компиляторах общего назначения.

Возможно, это некрасиво, но разработчики GCC считают, что это будет быстрее для длинных массивов. Если ваши массивы на самом деле не длинны, или если вы не можете нести идею скомпилированного кода, выглядящего так, отключите эту конкретную оптимизацию. Компиляция с помощью -O2 должна делать это (не проверено).