19

我很好奇是否有人可以为我阐明这一点。我正在研究一些数字数据转换的东西,并且我有几个进行数据转换的函数,我使用两个宏定义它们:

#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   

任何人都可以阐明浮动->双转换的幕后情况吗?也许如何编写它来获得更有效的组装?如果这很重要,我正在使用 gcc 4.6.3。

4

2 回答 2

7

你所说的“怪物”实际上看起来像自动矢量化代码。在这种技术开始运作良好并在通用编译器中有用之前,已经进行了 20 年的研究。

它可能不漂亮,但 GCC 实现者认为长数组会更快。如果您的数组实际上并不长,或者如果您无法忍受编译后的代码看起来像这样的想法,请禁用该特定优化。编译-O2应该这样做(未尝试)。

于 2012-08-26T23:36:02.603 回答
6

这里有几件事我可以很快看到(代码有点长,时间有点晚,而且我不是 AT&T 语法的粉丝)。

首先,第二个循环被矢量化(但很糟糕,见下文)。这本质上会导致一些代码膨胀——它现在必须处理比向量短的“尾端”等。

其次,float to double 是一种扩大的转换。这对标量无关紧要,但是对于向量,这意味着您不能只读取一些数据,对其进行转换并将其写回 - 在某些地方,您最终会得到双倍的字节数,并且必须处理它们和。(因此movhlps %xmm0,%xmm1

实际的循环仅从 402098h 跨越到 4020cfh,下面是“尾部处理”,上面是一个怪物,测试它是否完全跳过主循环以及一些我还没有完全弄清楚的东西 - 它会让感觉它是否是为了对齐,但我看不到任何test rdi, 15' 在那里,也没有任何明显的东西可以摆脱不对齐的开始。

第三,海湾合作委员会是跛脚的。这并不罕见。似乎认为 xmm3 以某种方式参与其中,但事实并非如此,而且似乎忘记了向量可以从内存中一次性加载测试对齐,这是它对未对齐指针的防御。无论如何,GCC 在这里做得不好。

于 2012-08-26T23:35:21.293 回答