【问题标题】:float to double conversion: why so many instructions?浮动到双重转换:为什么有这么多指令?
【发布时间】:2012-08-21 12:08:18
【问题描述】:

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

#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++);                              \
        }                                                               \
    } 

当我定义一个浮点到整数的转换时:

 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->double 转换的幕后情况?也许如何编写它来获得更有效的组装?如果这很重要,我正在使用 gcc 4.6.3。

【问题讨论】:

  • 您是否使用 64 位编译器进行编译?如果没有,它可能无法看到 64 位扩展。
  • @Nirk 抱歉,是的,我在 64 位 Ubuntu 上,gcc 的默认目标显示为 x86_64-linux-gnu
  • 顺便说一句,有两个宏是多余的;您可以将标识函数(作为宏,即#define IDENT(x) (x))传递给CONV_VIA_FUNC 以获取CONV_VIA_CAST...
  • 看起来编译器可能在可能的情况下为对齐和矢量化做了一些丑陋的特殊情况。您启用了什么优化级别?
  • @R,好的提示,谢谢,我最初只有一个函数可以转换所有内容,然后决定使用 lrint 函数,所以我添加了第二个宏。我正在使用 -O3 进行优化(也尝试过 -ffast-math 但没有区别)

标签: c assembly compiler-optimization


【解决方案1】:

你所谓的“怪物”实际上看起来像automatically vectorized code。在这种技术开始很好地工作并在通用编译器中有用之前,已经对这种技术进行了大约 20 年的研究。

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

【讨论】:

  • 果然 -O2 给出了类似于 float->int 的情况。
【解决方案2】:

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

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

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

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

第三,GCC 很蹩脚。这并不罕见。似乎认为 xmm3 以某种方式参与其中,但事实并非如此,并且似乎忘记了可以将向量从内存中加载到一个片段中-这又可能是因为一开始的怪物真的 没有测试对齐,这是它对未对齐指针的防御。但无论如何,GCC 在这方面做得不好。

【讨论】:

  • 我认为这有帮助。如果loop真的只占中间的小部分,我觉得合理很多。
  • @gct 如果是我而不是 GCC 实现这个,我会在那里有 6 个循环。一个用于 src 和 dst 都对齐时,一个用于其中一个对齐,另一个用于 8 对齐,两个用于处理所有其他未对齐(一个小循环首先对齐 dst 和一个循环来完成实际工作),以及处理尾部的循环。而且我还有一个 SSSE3 版本,它使用 palignr 来处理两个特殊的未对齐情况,而不是使用两个班次和一个或。总而言之,这是一个更大的混乱,但它会很快。如果你愿意,我可以给你看这个。
  • 在这种特殊情况下,我碰巧知道这两个指针总是对齐的(数据指针是页面对齐的,而视图指针是它的 2 次幂),所以也许我可以将它们标记为对齐以使 GCC 受益吗?
  • 对不起,我撒谎了,我使用的是上一个问题中的 -O2,看起来对齐属性没有任何改变。
  • @gct 你用的是 16 对吧?不是您在评论消失之前提到的 8?
猜你喜欢
  • 1970-01-01
  • 2014-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-10
  • 2017-09-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多