c++ - g++ 与手动优化复数乘法

标签 c++ gcc optimization clang

在我们的代码库中,我们有很多操作,例如 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. “编译器无论如何都会优化它”。

然而,正如某些性能回归所显示的那样,第三个论点是站不住脚的。比较这两个函数时(请参阅下面的 list ),智能版本始终优于 -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

智能版本在我的机器 (gcc-5.4) 上快了大约 4 倍,并且只有当任务随着数组大小的增加而变得越来越受内存限制时,加速才会下降到因子 2。

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

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

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

list :

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()};
    }
}

主要.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 进行编译可获得快速性能。

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 编译器标志。 According to the documentation , 此标志用于

allow optimizations for floating-point arithmetic that (a) assume that arguments and results are valid and (b) may violate IEEE or ANSI standards. When

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

Allows optimizations for floating-point arithmetic that ignore the signedness of zero. IEEE arithmetic specifies the behavior of distinct +0.0 and -0.0 values, which then prohibits simplification of expressions such as x+0.0 or 0.0*x (even with -ffinite-math-only). This option implies that the sign of a zero result isn’t significant.

关于c++ - g++ 与手动优化复数乘法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49189243/

相关文章:

c++ - 编写计算 nCr 值的程序 (C++)

c++ - 警告 C4996 : This function or variable may be unsafe -- compared to GCC on POSIX

c# - 尝试求和时 LINQ 与 foreach

c++ - x >= 0 是否比 x > -1 更有效?

c++ - 为什么内联函数需要传递参数?

c++ - 传递给模板的 Lambda 未定义

c++ - 泛化 C++ 模式

gcc - 链接描述文件中的位置计数器 (.) 更新

mysql 查询优化

c++ - 多态,为什么我做不到?