【发布时间】:2018-08-17 18:12:27
【问题描述】:
在我们的代码库中,我们有很多类似 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()};
}
}
一开始,我对这个“智能”版本不屑一顾,因为
- 很难理解。
- 出现错误的概率更高。
- “编译器无论如何都会优化它”。
但是,正如一些性能回归表明的那样,第三个论点并不成立。在比较这两个函数时(参见下面的列表),智能版本的性能始终优于 -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
【问题讨论】:
-
jomega不是const。此外,operator *最有可能采用const &并被允许将const_cast和const分开并更改jomega,因此不能确定jomega的真实部分是0.0。看看将const添加到jomega是否有任何作用。 -
添加
const并没有多大作用。 -
@nwp 我不认为将某些东西声明为
const有助于编译器优化您的代码。0.0是文字,无论如何都应该进行持续传播。有一个旧的 gotw 声明 const 不会为优化做任何事情(constexpr 当然可以):gotw.ca/gotw/081.htm. -
@Jens
const不做任何优化通常只适用于添加const(例如,采用const &或&的函数并不重要),但是顶级const实际上意味着const,因为将其丢弃是UB。那显然是我错了,它在这里也没有多大作用。
标签: c++ gcc optimization clang