繁体   English   中英

对于复数乘法,g ++与手动优化

[英]g++ vs. optimization by hand for complex number multiplication

在我们的代码库中,我们有很多操作,如j *ω* X,其中j是虚数单位,ω是实数,X是复数。 实际上很多循环看起来像:

#include <complex>
#include <vector>

void mult_jomega(std::vector<std::complex<double> > &vec, double omega){
    std::complex<double> jomega(0.0, omega);
    for (auto &x : vec){
        x*=jomega;
    }
}

但是,我们利用jomega的实部为零的事实并将乘法写为:

void mult_jomega_smart(cvector &vec, double omega){
    for (auto &x : vec){
        x={-omega*x.imag(), omega*x.real()};
    }
}

一开始,我对这种“聪明”的转变不屑一顾,因为

  1. 它更难理解。
  2. 错误的概率更高。
  3. “无论如何,编译器都会优化它”。

然而,正如一些表现回归所显示的那样,第三个论点并不成立。 在比较这两个函数时(请参阅下面的列表),智能版本优于-O2以及-O3

size    orig(musec)   smart(musec)  speedup
10      0.039928      0.0117551     3.39665
100     0.328564      0.0861379     3.81439
500     1.62269       0.417475      3.8869
1000    3.33012       0.760515      4.37877
2000    6.46696       1.56048       4.14422
10000   32.2827       9.2361        3.49528
100000  326.828       115.158       2.8381
500000  1660.43       850.415       1.95249

智能版本在我的机器上快了大约4倍(gcc-5.4),并且随着任务变得越来越多,随着阵列尺寸的增加,内存受限,加速下降到因子2。

我的问题是,什么阻止编译器优化不太智能但更易读的版本,毕竟编译器可以看到, jomega的真实部分是零? 是否可以通过提供一些额外的编译标志来帮助编译器进行优化?

注意:其他编译器也存在加速:

compiler      speedup
g++-5.4          4
g++-7.2          4
clang++-3.8      2  [original version 2-times faster than gcc]

人数:

mult.cpp - 防止内联:

#include <complex>
#include <vector>

typedef std::vector<std::complex<double> > cvector;
void mult_jomega(cvector &vec, double omega){
    std::complex<double> jomega(0.0, omega);
    for (auto &x : vec){
        x*=jomega;
    }
}

void mult_jomega_smart(cvector &vec, double omega){
    for (auto &x : vec){
        x={-omega*x.imag(), omega*x.real()};
    }
}

main.cpp中:

#include <chrono>
#include <complex>
#include <vector>
#include <iostream>

typedef std::vector<std::complex<double> > cvector;
void mult_jomega(cvector &vec, double omega);
void mult_jomega2(cvector &vec, double omega);
void mult_jomega_smart(cvector &vec, double omega);


const size_t N=100000;   //10**5
const double OMEGA=1.0;//use 1, so nothing changes -> no problems with inf & Co

void compare_results(const cvector &vec){
   cvector m=vec;
   cvector m_smart=vec;
   mult_jomega(m, 5.0);
   mult_jomega_smart(m_smart,5.0);
   std::cout<<m[0]<<" vs "<<m_smart[0]<<"\n";
   std::cout<< (m==m_smart ? "equal!" : "not equal!")<<"\n";

}

void test(size_t vector_size){

     cvector vec(vector_size, std::complex<double>{1.0, 1.0});

     //compare results, triger if in doubt
     //compare_results(vec);


     //warm_up, just in case:
     for(size_t i=0;i<N;i++)
        mult_jomega(vec, OMEGA);

     //test mult_jomega:
     auto begin = std::chrono::high_resolution_clock::now();
     for(size_t i=0;i<N;i++)
        mult_jomega(vec, OMEGA);
     auto end = std::chrono::high_resolution_clock::now();
     auto time_jomega=std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin).count()/1e3;


     //test mult_jomega_smart:
     begin = std::chrono::high_resolution_clock::now();
     for(size_t i=0;i<N;i++)
        mult_jomega_smart(vec, OMEGA);
     end = std::chrono::high_resolution_clock::now();
     auto time_jomega_smart=std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin).count()/1e3;

     double speedup=time_jomega/time_jomega_smart;
     std::cout<<vector_size<<"\t"<<time_jomega/N<<"\t"<<time_jomega_smart/N<<"\t"<<speedup<<"\n";
}


int main(){
   std::cout<<"N\tmult_jomega(musec)\tmult_jomega_smart(musec)\tspeedup\n";    
   for(const auto &size : std::vector<size_t>{10,100,500,1000,2000,10000,100000,500000})
        test(size);          
}

建设和运营:

g++ main.cpp mult.cpp -O3 -std=c++11 -o mult_test
./mult_test

使用标志-ffast-math编译-ffast-math快速的性能。

N       mult_jomega(musec)      mult_jomega_smart(musec)        speedup
10      0.00860809              0.00818644                      1.05151
100     0.0706683               0.0693907                       1.01841
500     0.29569                 0.297323                        0.994509
1000    0.582059                0.57622                         1.01013
2000    1.30809                 1.24758                         1.0485
10000   7.37559                 7.4854                          0.98533

编辑 :更具体地说,它是-funsafe-math-optimizations编译器标志。 根据文档 ,这个标志用于

允许优化浮点运算,(a)假设参数和结果有效,(b)可能违反IEEE或ANSI标准。 什么时候

编辑2 :更具体地说,它是-fno-signed-zeros选项,其中:

允许优化浮点运算,忽略零的符号。 IEEE算法指定了不同的+0.0-0.0值的行为,然后禁止简化表达式,如x+0.00.0*x (即使使用-ffinite-math-only )。 此选项意味着零结果的符号不重要。

我对使用godbolt编译器资源管理器的Aziz回答中提到的编译器选项进行了更多调查。 示例代码实现了内部循环的三个版本:

  1. mult_jomega示例。
  2. 同一循环的手写版本,其中对operator*=的调用已被计算所取代
  3. mult_jomega_smart示例

来自godbolt的代码

// 1. mult_jomega
std::complex<double> const jomega(0.0, omega);
for (auto &x : v){
    x*=jomega;
}

// 2. hand-written mult_jomega
for (auto &x : v3){
    double x1 = x.real() * jomega.real();
    double x2 = x.imag() * jomega.imag();
    double x3 = x.real() * jomega.imag();
    double x4 = x.imag() * jomega.real();
    x = { x1 - x2 , x3 + x4};
}

// 3. mult_jomega_smart
for (auto &x : v2){
    x={-omega*x.imag(), omega*x.real()};
}

检查三个循环的汇编程序代码:

mult_jomega

 cmp %r13,%r12
 je 4008ac <main+0x10c>
 mov %r12,%rbx
 nopl 0x0(%rax)
 pxor %xmm0,%xmm0
 add $0x10,%rbx
 movsd -0x8(%rbx),%xmm3
 movsd -0x10(%rbx),%xmm2
 movsd 0x8(%rsp),%xmm1
 callq 400740 <__muldc3@plt>
 movsd %xmm0,-0x10(%rbx)
 movsd %xmm1,-0x8(%rbx)
 cmp %rbx,%r13
 jne 400880 <main+0xe0>

手写乘法

 cmp %rdx,%rdi
 je 40090c <main+0x16c>
 pxor %xmm3,%xmm3
 mov %rdi,%rax
 movsd 0x8(%rsp),%xmm5
 nopl 0x0(%rax,%rax,1)
 movsd (%rax),%xmm0
 movapd %xmm5,%xmm4
 movsd 0x8(%rax),%xmm1
 add $0x10,%rax
 movapd %xmm0,%xmm2
 mulsd %xmm5,%xmm0
 mulsd %xmm1,%xmm4
 mulsd %xmm3,%xmm2
 mulsd %xmm3,%xmm1
 subsd %xmm4,%xmm2
 addsd %xmm1,%xmm0
 movsd %xmm2,-0x10(%rax)
 movsd %xmm0,-0x8(%rax)
 cmp %rax,%rdx
 jne 4008d0 <main+0x130>

mult_jomega_smart

cmp %rcx,%rdx
 je 400957 <main+0x1b7>
 movsd 0x8(%rsp),%xmm2
 mov %rcx,%rax
 xorpd 0x514(%rip),%xmm2 # 400e40 <_IO_stdin_used+0x10>
 nopl 0x0(%rax)
 add $0x10,%rax
 movsd 0x8(%rsp),%xmm0
 movsd -0x8(%rax),%xmm1
 mulsd -0x10(%rax),%xmm0
 mulsd %xmm2,%xmm1
 movsd %xmm1,-0x10(%rax)
 movsd %xmm0,-0x8(%rax)
 cmp %rax,%rdx
 jne 400930 <main+0x190>

我对汇编代码的理解非常有限,但我明白了

  • operator*=没有在mult_jomega内联
  • x1x4得到计算,尽管它们总是0.0,因为jomega.real()==0.0

我不知道为什么operator*= doe snot内联。 源代码是直接的,仅包含三行。

当您认为对于double类型的值, 0.0 * x == 0.0并不总是为真时,可以解释x1x4的计算。 除了在另一个答案中提到的带符号的零定义之外,还有无限值naninf ,其中x * 0.0 = 0.0不成立。

如果使用-fno-signed-zeros-ffinite-math-only编译,则应用优化并删除x1x4计算。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM