当前位置: 首页 > 知识库问答 >
问题:

为什么clang使地震快速平方根反码比GCC快10倍?(带*(长*)浮点型双关)

蔡鸿骞
2023-03-14

我试图对快速平方根逆进行基准测试。完整代码如下:

#include <benchmark/benchmark.h>
#include <math.h>

float number = 30942;
    
static void BM_FastInverseSqrRoot(benchmark::State &state) {
    for (auto _ : state) {
        // from wikipedia:
        long i;
        float x2, y;
        const float threehalfs = 1.5F;

        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;
        i  = 0x5f3759df - ( i >> 1 );
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );
        //  y  = y * ( threehalfs - ( x2 * y * y ) );
        
        float result = y;
        benchmark::DoNotOptimize(result);
    }
}


static void BM_InverseSqrRoot(benchmark::State &state) {
    for (auto _ : state) {
        float result = 1 / sqrt(number);
        benchmark::DoNotOptimize(result);
    } 
}

BENCHMARK(BM_FastInverseSqrRoot);
BENCHMARK(BM_InverseSqrRoot);

如果你想自己运行,这是quick bench中的代码。

使用GCC 11.2和-O3编译时,BM\u FastInverseSqrRoot的速度大约是Noop的31倍(当我在我的机器上本地运行它时,速度大约为10 ns)。使用Clang 13.0和-O3编译,速度大约是Noop的3.6倍(当我在我的机器上本地运行它时,速度大约为1ns)。这是10倍的速度差。

这是相关组件(取自快速工作台)。

有GCC:

               push   %rbp
               mov    %rdi,%rbp
               push   %rbx
               sub    $0x18,%rsp
               cmpb   $0x0,0x1a(%rdi)
               je     408c98 <BM_FastInverseSqrRoot(benchmark::State&)+0x28>
               callq  40a770 <benchmark::State::StartKeepRunning()>
  408c84       add    $0x18,%rsp
               mov    %rbp,%rdi
               pop    %rbx
               pop    %rbp
               jmpq   40aa20 <benchmark::State::FinishKeepRunning()>
               nopw   0x0(%rax,%rax,1)
  408c98       mov    0x10(%rdi),%rbx
               callq  40a770 <benchmark::State::StartKeepRunning()>
               test   %rbx,%rbx
               je     408c84 <BM_FastInverseSqrRoot(benchmark::State&)+0x14>
               movss  0x1b386(%rip),%xmm4        # 424034 <_IO_stdin_used+0x34>
               movss  0x1b382(%rip),%xmm3        # 424038 <_IO_stdin_used+0x38>
               mov    $0x5f3759df,%edx
               nopl   0x0(%rax,%rax,1)
   408cc0      movss  0x237a8(%rip),%xmm0        # 42c470 <number>
               mov    %edx,%ecx
               movaps %xmm3,%xmm1
        2.91%  movss  %xmm0,0xc(%rsp)
               mulss  %xmm4,%xmm0
               mov    0xc(%rsp),%rax
        44.70% sar    %rax
        3.27%  sub    %eax,%ecx
        3.24%  movd   %ecx,%xmm2
        3.27%  mulss  %xmm2,%xmm0
        9.58%  mulss  %xmm2,%xmm0
        10.00% subss  %xmm0,%xmm1
        10.03% mulss  %xmm2,%xmm1
        9.64%  movss  %xmm1,0x8(%rsp)
        3.33%  sub    $0x1,%rbx
               jne    408cc0 <BM_FastInverseSqrRoot(benchmark::State&)+0x50>
               add    $0x18,%rsp
               mov    %rbp,%rdi
               pop    %rbx
               pop    %rbp
  408d0a       jmpq   40aa20 <benchmark::State::FinishKeepRunning()>

叮当声:

           push   %rbp
           push   %r14
           push   %rbx
           sub    $0x10,%rsp
           mov    %rdi,%r14
           mov    0x1a(%rdi),%bpl
           mov    0x10(%rdi),%rbx
           call   213a80 <benchmark::State::StartKeepRunning()>
           test   %bpl,%bpl
           jne    212e69 <BM_FastInverseSqrRoot(benchmark::State&)+0x79>
           test   %rbx,%rbx
           je     212e69 <BM_FastInverseSqrRoot(benchmark::State&)+0x79>
           movss  -0xf12e(%rip),%xmm0        # 203cec <_IO_stdin_used+0x8>
           movss  -0xf13a(%rip),%xmm1        # 203ce8 <_IO_stdin_used+0x4>
           cs nopw 0x0(%rax,%rax,1)
           nopl   0x0(%rax)
 212e30 2.46%  movd   0x3c308(%rip),%xmm2        # 24f140 <number>
        4.83%  movd   %xmm2,%eax
        8.07%  mulss  %xmm0,%xmm2
        12.35% shr    %eax
        2.60%  mov    $0x5f3759df,%ecx
        5.15%  sub    %eax,%ecx
        8.02%  movd   %ecx,%xmm3
        11.53% mulss  %xmm3,%xmm2
        3.16%  mulss  %xmm3,%xmm2
        5.71%  addss  %xmm1,%xmm2
        8.19%  mulss  %xmm3,%xmm2
        16.44% movss  %xmm2,0xc(%rsp)
        11.50% add    $0xffffffffffffffff,%rbx
               jne    212e30 <BM_FastInverseSqrRoot(benchmark::State&)+0x40>
 212e69        mov    %r14,%rdi
               call   213af0 <benchmark::State::FinishKeepRunning()>
               add    $0x10,%rsp
               pop    %rbx
               pop    %r14
               pop    %rbp
  212e79       ret

他们看起来很像我。两者似乎都在使用SIMD寄存器/指令,如mulss。GCC版本的sar估计占46%?(但我认为这只是标签错误,是mulss、mov、sar加起来占了46%)。无论如何,我对汇编还不够熟悉,无法真正说出是什么导致了如此巨大的性能差异。

有人知道吗?

共有1个答案

白浩荡
2023-03-14

仅供参考,现在在x86-64上仍然值得使用地震快速平方根逆算法吗不,被SSE1淘汰,可以使用或不使用牛顿迭代。

正如人们在评论中指出的,您使用的是64位long(因为这是非Windows系统上的x86-64),将其指向32位浮点数。因此除了违反严格的混淆现象(使用memcpystd::bit_cast

您的perf报告输出确认了这一点;GCC正在执行32位movss%xmm0,0xc(%rsp)存储到堆栈,然后重新加载64位mov 0xc(%rsp),%rax,这将导致存储转发失速,从而花费更多额外的延迟。还有一个吞吐量损失,因为实际上你是在测试吞吐量,而不是延迟:逆sqrt的下一次计算只有一个常量输入,而不是上一次迭代的结果。(基准测试::DoNotOptimize包含一个"内存"Clobber,它阻止GCC/clang将大部分计算提升到循环之外;他们必须假设number可能已经改变,因为它不是const。)

与往常一样,等待加载结果的指令(sar)是这些周期的罪魁祸首。(当中断触发以在事件计数器周围循环时采集样本时,CPU必须找出导致该事件的一条指令。通常这条指令最终会等待较早的慢指令,或者可能只是在慢指令之后等待,即使没有数据依赖性,我忘了。)

将问题中快速工作台链接上的代码修复为使用int32\u t和std::bit\u cast,https://godbolt.org/z/qbxqsaW4e显示GCC和clang编译类似于AST,尽管不完全相同。e、 g.GCC加载数两次,一次加载到整数寄存器,一次加载到XMM0。Clang加载一次,并使用movd eax、xmm2来获取它。

关于QB(https://quick-bench.com/q/jYLeX2krrTs0afjQKFp6Nm_G2v8),现在GCC的BM\u FastInversescroot比原始版本快了2倍,没有ffast math

是的,由于C从sqrt(浮点)中推断出sqrtf,原始基准编译为无ffast数学的SQRTS。它会检查数字是否为<代码>

快速工作台确实允许使用AST和牛顿迭代。(如果FMA可用,速度会更快,但quick bench不允许使用-march=native或任何东西。我想可以使用\uu attribute\((target(“avx,FMA”)))

无论我是否使用,Quick bench现在都会给出错误或超时,并带有权限错误映射页面。建议使用较小的-m/-mmap_页面,因此我无法在该系统上进行测试。

带有牛顿迭代的rsqrt(就像编译器在-OFast中使用的那样)可能更快或类似于Quake的快速invsqrt,但精度约为23位。

 类似资料:
  • 问题内容: 我对以下算法有一些疑问,可以判断数字是否为质数,我也知道使用Eratosthenes筛子可以更快地响应。 为什么计算速度更快。比只有一次? 为什么比我的方法快? 这些算法O(n),O(sqrt(n)),O(n log(n))的复杂度是多少? 这也是我的代码的链接:http : //ideone.com/Fapj1P 问题答案: 查看下面的复杂性。计算平方根的额外费用。 Math.sqr

  • 问题 你想快速计算某数的平方根倒数。 解决方案 在 Quake Ⅲ Arena 的源代码中,这个奇怪的算法对一个幻数进行整数运算,来计算平方根倒数的浮点近似值。 在 CoffeeScript 中,他使用经典原始的变量,以及由 Chris Lomont 发现的新的最优 32 位幻数。除此之外,还使用 64 位大小的幻数。 另一特征是可以通过控制牛顿迭代法的迭代次数来改变其精确度。 相比于传统的,该算

  • 很长一段时间以来,我一直认为C比JavaScript快。然而,今天我制作了一个基准脚本来比较两种语言的浮点计算速度,结果令人惊叹! JavaScript似乎比C快近4倍! 我让这两种语言在我的i5-430M笔记本电脑上做同样的工作,执行了100000000次。C需要大约410毫秒,而JavaScript只需要大约120毫秒。 我真的不知道为什么JavaScript在这种情况下运行得这么快。有人能解

  • 我在使用clang的代码完成机制时正在研究潜在的代码完成加速。下面描述的流程是我在Anders Bakken的rtages中发现的。 翻译单元由守护程序监控文件解析以查找更改。这是通过调用和相关函数(,)完成的。当用户在源文件中的给定行和列处请求完成时,守护程序将源文件的最后保存版本和当前源文件的缓存翻译单元传递到。(Clang Code完整文档)。 传递给(来自CompletionThread:

  • 问题内容: 在更新xCode 6之前,我没有问题将双精度型转换为字符串,但是现在它给了我一个错误 它给我错误信息“双精度不能转换为字符串”。还有其他方法吗? 问题答案: 它不是强制转换,而是根据具有格式的值创建字符串。 格式不同:

  • 问题内容: 示例代码在这里 问题答案: 我认为速度更快,因为使用矢量化方式和熊猫构建在此数组上。 慢,因为它使用。 操作是最快的,然后是。 请参阅此答案,并更好地解释pandas开发人员。