C++ 迭代Kahan求和的优化实现

C++ 迭代Kahan求和的优化实现,c++,x86-64,sse,inline-assembly,fast-math,C++,X86 64,Sse,Inline Assembly,Fast Math,简介 Kahan求和/补偿求和是一种解决编译器无法尊重数字的关联特性的技术。截断误差导致(a+b)+c不完全等于a+(b+c),从而在较长的和序列上累积了不希望的相对误差,这是科学计算中的一个常见障碍 任务 我希望Kahan求和的最佳实现。我怀疑最好的性能可能是通过手工编制的汇编代码实现的 尝试 下面的代码使用三种方法计算[0,1]范围内1000个随机数的总和 标准求和:将均方根相对误差累加为O(sqrt(N))的简单实现 Kahan求和[g++]:使用c/c++函数“csum”进行补偿求和。评

简介
Kahan求和/补偿求和是一种解决编译器无法尊重数字的关联特性的技术。截断误差导致(a+b)+c不完全等于a+(b+c),从而在较长的和序列上累积了不希望的相对误差,这是科学计算中的一个常见障碍

任务
我希望Kahan求和的最佳实现。我怀疑最好的性能可能是通过手工编制的汇编代码实现的

尝试
下面的代码使用三种方法计算[0,1]范围内1000个随机数的总和

  • 标准求和:将均方根相对误差累加为O(sqrt(N))的简单实现

  • Kahan求和[g++]:使用c/c++函数“csum”进行补偿求和。评论中的解释。请注意,某些编译器可能具有使此实现无效的默认标志(请参见下面的输出)

  • Kahan求和[asm]:使用与“csum”相同的算法将补偿求和实现为“csumasm”。评论中的隐晦解释

  • #包括
    #包括
    #包括
    外部“C”无效csumasm(双重、双重、双重);
    __asm__(
    “csumasm:\n”
    “movsd(%rcx),%xmm0\n”//xmm0=a
    “subsd(%r8),%xmm1\n”//xmm1-r8(c)| y=b-c
    movapd%xmm0,%xmm2\n
    “addsd%xmm1,%xmm2\n”//xmm2+xmm1(y)| b=a+y
    movapd%xmm2,%xmm3\n
    “subsd%xmm0,%xmm3\n”//xmm3-xmm0(a)| b-a
    movapd%xmm3,%xmm0\n
    “subsd%xmm1,%xmm0\n”//xmm0-xmm1(y)|-y
    “movsd%xmm0,(%r8)\n”//xmm0到c
    “movsd%xmm2,(%rcx)\n”//b到a
    “ret\n”
    );
    void csum(double&a,double b,double&c){//此函数添加a和b,并将c作为补偿项传递
    双y=b-c;//y是b参数的修正
    b=a+y;//将已更正的b参数添加到参数中。当前总和的输出
    c=(b-a)-y;//查找要作为补偿项传递的新错误
    a=b;
    }
    双重乐趣(双重fMin,双重fMax){
    双f=(双)rand()/rand_MAX;
    返回fMin+f*(fMax-fMin);//返回随机值
    }
    int main(int argc,字符**argv){
    int N=1000;
    srand(0);//对每个方法使用0种子
    双sum1=0;
    对于(int n=0;n
    带有-O3的输出为:

    Standard summation:
     5.1991955320902093e+002 (error: -3.4106051316484809e-013)
    
    Kahan compensated summation [g++]:
     5.1991955320902127e+002 (error: 0.0000000000000000e+000)
    
    Kahan compensated summation [asm]:
     5.1991955320902127e+002
    
    使用-O3-ffast math进行输出

    Standard summation:
     5.1991955320902093e+002 (error: -3.4106051316484809e-013)
    
    Kahan compensated summation [g++]:
     5.1991955320902093e+002 (error: -3.4106051316484809e-013)
    
    Kahan compensated summation [asm]:
     5.1991955320902127e+002
    
    很明显,-ffast math破坏了Kahan求和算法,这很不幸,因为我的程序需要使用-ffast math

    问题

  • 是否有可能为Kahan的补偿求和构造更好/更快的asm x64代码?也许有一种聪明的方法可以跳过一些movapd指令

  • <> LI> < P>如果没有更好的ASM代码,有没有一种C++方法来实现卡亨求和,它可以与F-FAST运算一起使用,而不必退化到天真的求和?也许C++实现通常更灵活,编译器可以优化。 欢迎提出意见或建议

    进一步信息

    • “fun”的内容不能内联,但“csum”函数可以内联
    • 总和必须作为一个迭代过程进行计算(校正项必须应用于每次加法)。这是因为预期的求和函数接受的输入取决于前一个求和
    • 预期的求和函数被无限期地每秒调用数亿次,这促使我们追求高性能的低级别实现
    • 由于性能原因,高精度算法(如long double、float128或任意精度库)不被视为高精度解决方案
    编辑:内联csum(没有完整的代码没有多大意义,但仅供参考)


    您可以将不需要使用
    -ffast-math
    的函数(如csum循环)放入单独的文件中,该文件在编译时不使用
    -ffast-math

    可能您也可以使用
    \uuuuuuuuuuuuuuu属性((优化(“无快速数学”))
    ,但遗憾的是,您说优化级别的杂注和属性“不适用于生产代码”

    更新:问题的一部分显然是基于一种误解,即
    -O3
    不安全,还是什么?它是;ISO C++指定FCC数学规则,就像GCC的代码> -FNO快速数学< /代码>。使用
    -O3
    编译所有内容显然可以使OP的代码快速安全地运行。请参阅此答案的底部,了解类似OpenMP的变通方法,以便在不实际启用
    -ffast math
    的情况下,为代码的某些部分获得快速数学的一些好处

    ICC默认为fast path,因此您必须专门启用FP=strict,以便使用-O3时安全,但不管其他优化设置如何,gcc/clang默认为完全严格的FP。(除了
    -Ofast
    =
    -O3-ffast数学


    通过保持一个(或四个)总计向量和相等数量的补偿向量,您应该能够对Kahan求和进行向量化。您可以使用intrinsic实现这一点(只要不为该文件启用快速数学)

    e、 g.使用SSE2
    \uuuu m128d
    进行2次包装a
            subsd   xmm0, QWORD PTR [rsp+32]
            movapd  xmm1, xmm3
            addsd   xmm3, xmm0
            movsd   QWORD PTR [rsp+16], xmm3
            subsd   xmm3, xmm1
            movapd  xmm1, xmm3
            subsd   xmm1, xmm0
            movsd   QWORD PTR [rsp+32], xmm1