c++ - 阅读汇编比较乘法与除法

标签 c++ assembly

<分区>

我想看看在我们的编译中是否有写作之间的收获

x/3.0

对比 const double one_over_three = 1.0/3.0; x * one_over_three;

但是我需要一些阅读汇编的帮助,我还没有学过。

我编译了

int div3(double x) {
  const double three = 1/3.0;
  return x*three;
}

得到了

    div3(double):
    pushq   %rbp
    movq    %rsp, %rbp
    movsd   %xmm0, -24(%rbp)
    movabsq $4599676419421066581, %rax
    movq    %rax, -8(%rbp)
    movsd   -24(%rbp), %xmm1
    movsd   .LC0(%rip), %xmm0
    mulsd   %xmm1, %xmm0
    cvttsd2si       %xmm0, %eax
    popq    %rbp
    ret
    .LC0:
    .long   1431655765
    .long   1070945621

然后编译

int div3(double x) {
  return x/3.0;
}

得到了

    div3(double):
    pushq   %rbp
    movq    %rsp, %rbp
    movsd   %xmm0, -8(%rbp)
    movsd   -8(%rbp), %xmm0
    movsd   .LC0(%rip), %xmm1
    divsd   %xmm1, %xmm0
    cvttsd2si       %xmm0, %eax
    popq    %rbp
    ret
    .LC0:
    .long   0
    .long   1074266112

Q1:有人可以帮我阅读程序集吗?

Q2:每个代码中最后两行的含义是什么?

最佳答案

如果这就是您进行优化的方式,那么您的做法错误。对不起,我在解释某事时通常不会使用否定词,但你走错了路。

你做的事情叫做过早优化和微优化。您正在尝试优化您不知道需要优化的东西。首先,这是一个破坏交易的因素:使用编译器优化。然后您通常会分析并尝试优化热点


让我们看看您尝试进行的优化的相关性如何:

我首先使用 -O3 (gcc 6.1) 编译:让我们看看输出(也在 Godbolt compiler explorer 上):

mul_inverse3(double):
        mulsd   .LC0(%rip), %xmm0
        cvttsd2si       %xmm0, %eax
        ret

div3(double):
        divsd   .LC1(%rip), %xmm0
        cvttsd2si       %xmm0, %eax
        ret

其中 .LCO.LC1 是常量:最接近 1/3.0 的 double3.0:

.LC0:
        .long   1431655765
        .long   1070945621
.LC1:
        .long   0
        .long   1074266112

好的,您已经看到编译器自己可以做多少事情了。这是您应该一直关注的内容,而不是嘈杂的 -O0 输出。

哪个更快?根据经验,在所有 CPU 上乘法都比除法快。该比率取决于特定的微体系结构。对于 x86,请参阅 Agner Fog's instruction tables and microarch pdf 标签 wiki 中的其他性能链接。例如,英特尔 Sandybridge 上的延迟约为 3 倍,吞吐量约为 1/12。而且,div 甚至可能不是瓶颈,因此 div 与 mul 可能不会影响整体性能。缓存未命中、其他管道停顿,甚至其他类似 IO 的事情都可以完全隐藏差异。


但它们还是有区别的。我们可以从编译器中获取相同的代码吗? 是!添加-ffast-math。使用此选项,编译器可以重新排列/更改 float 操作,即使它稍微改变了结果(这是您尝试手动执行的操作)。不过要小心,因为这适用于整个程序。

mul_inverse3(double):
        mulsd   .LC0(%rip), %xmm0
        cvttsd2si       %xmm0, %eax
        ret

div3(double):
        mulsd   .LC0(%rip), %xmm0
        cvttsd2si       %xmm0, %eax
        ret

我们可以做更多吗?是:添加 -march=native 并在编译器文档中搜索更多开关。

所以这是第一课:让编译器首先进行优化!


这是第二个:

您花了 1 周时间尝试优化随机操作。你终于做到了!经过一周的辛勤工作和不眠之夜,您的操作速度提高了 10 倍(哇!恭喜)。然后你运行你的程序,发现程序只快了 1%。惊恐的事件!这怎么可能?好吧,如果您的程序只花费 1% 的时间来执行该操作,那么……您明白了。或者可以有其他机制,如操作系统优化、CPU 管道等,使操作重复 100 次消耗远少于 100 倍。您需要做的是首先分析!找到热循环并对其进行优化!优化程序花费 60% 时间的循环/函数。

更重要的是,寻找高级优化,这样您的代码就可以做更少的工作,而不是更快地完成同样的工作。

关于c++ - 阅读汇编比较乘法与除法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38977240/

相关文章:

c++ - 为什么 std::map 在按值传递给 lambda 时表现异常?

c++ - 复制语义和比较

c++ - 编译器使用哪些真正的规则集来决定是否内联函数?

c++ - Winsock发送消息两次C++

c++ - 如何在函数内调用函数?

assembly - 在编译器中实现闭包

assembly - 使用Intel SIMD SSE加载非连续值

performance - 组装一些与 nasm 宏不相符的代码

memory - 如何在WinMobile6上启用ARMv6非对齐访问?

c++ - MSVC编译器生成mov ecx, ecx看起来没什么用