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. “无论如何编译器都会对其进行优化”。

但是,正如一些性能回归表明的那样,第三个论点并不成立。在比较这两个函数时(参见下面的列表),智能版本的性能始终优于 -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]

列表:

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 标志编译会带来快速的性能。

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

Edit 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/

相关文章:

string - 字符串中最大连续1个数字

javascript - 优化JavaScript setTimeout

c++ - 带循环递归(生成数据)

c++ - 函数候选和声明顺序

c - 用GnuTLS构建wget?

c++ - 就嵌入式系统的大小而言,我可以获得像 clang 或 gcc 这样的成熟编译器多小?

linux - 与PolarSSL交叉编译OpenVPN吗?

c++ - istream& 运算符的问题 >>

c++ - 条件变量函数未在此范围内声明

delphi - 使用 Delphi 快速搜索以查看大文件中是否存在字符串