#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
中,看看是否有任何变化。 - nwpconst
没有太大作用。 - Azizconst
并不能帮助编译器优化你的代码。0.0
是一个字面量,常量传播应该无论如何都会发生。有一个旧的gotw声明说const对于优化没有任何作用(当然constexpr有):http://www.gotw.ca/gotw/081.htm。 - Jensconst
对于优化并没有什么作用(比如,一个函数使用const &
或者&
并没有太大区别),但是顶层的const
实际上是意味着不可变的,因为强制转换会导致未定义行为。话虽如此,显然我错了,在这里也没有什么作用。 - nwp