我在Python3中有一些代码(带有numpy),我想将其转换为C++(使用eigen3),以便获得更高效的程序。因此,我决定测试一个简单的示例来评估我将获得的性能提升。该代码由两个要按系数相乘的随机数组组成。我的结论是,使用 numpy 的 python 代码比 C++ 中的代码快约 30%。我想知道为什么解释的python代码比编译的C++代码更快。我在C++代码中缺少某些内容吗?
我正在使用gcc 9.1.0,Eigen 3.3.7,Python 3.7.3和Numpy 1.16.4。
可能的解释:
C++程序没有使用矢量化
Numpy 比我想象的
要优化得多 时间在每个程序中
测量不同的东西
在堆栈溢出中有一个类似的问题(特征矩阵与Numpy数组乘法性能)。我在计算机中对此进行了测试,并得到了预期的结果,即特征比 numpy 更有效,但这里的操作是矩阵乘法而不是系数乘法。
Python 代码 (main.py)
执行命令:python3 main.py
import numpy as np
import time
Lx = 4096
Ly = 4000
# Filling arrays
a = np.random.rand(Lx, Ly).astype(np.float64)
a1 = np.random.rand(Lx, Ly).astype(np.float64)
# Coefficient-wise product
start = time.time()
b = a*a1
# Compute the elapsed time
end = time.time()
print(b.sum())
print("duration: ", end-start)
使用 eigen3(main_eigen.cpp)
编译命令C++代码:g++ -O3 -I/usr/include/eigen3/main_eigen.cpp -o prog_eigen
#include <iostream>
#include <chrono>
#include "Eigen/Dense"
#define Lx 4096
#define Ly 4000
typedef double T;
int main(){
// Allocating arrays
Eigen::Array<T, -1, -1> KPM_ghosts(Lx, Ly), KPM_ghosts1(Lx, Ly), b(Lx,Ly);
// Filling the arrays
KPM_ghosts.setRandom();
KPM_ghosts1.setRandom();
// Coefficient-wise product
auto start = std::chrono::system_clock::now();
b = KPM_ghosts*KPM_ghosts1;
// Compute the elapsed time
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "sn";
// Print the sum so the compiler doesn't optimize the code away
std::cout << b.sum() << "n";
return 0;
}
纯C++代码 (main.cpp)
编译命令: g++ -O3 main.cpp -o prog
#include <iostream>
#include <chrono>
#define Lx 4096
#define Ly 4000
#define N Lx*Ly
typedef double T;
int main(){
// Allocating arrays
T lin_vector1[N];
T lin_vector2[N];
T lin_vector3[N];
// Filling the arrays
for(unsigned i = 0; i < N; i++){
lin_vector1[i] = std::rand()*1.0/RAND_MAX;
lin_vector2[i] = std::rand()*1.0/RAND_MAX;
}
// Coefficient-wise product
auto start = std::chrono::system_clock::now();
for(unsigned i = 0; i < N; i++)
lin_vector3[i] = lin_vector1[i]*lin_vector2[i];
// Compute the elapsed time
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "sn";
// Print the sum so the compiler doesn't optimize the code away
double sum = 0;
for(unsigned i = 0; i < N; i++)
sum += lin_vector3[i];
std::cout << "sum: " << sum << "n";
return 0;
}
每个程序的运行时间10次
普通C++运行时间:0.210664s 运行时间:0.215406s 运行时间:0.222483s 运行时间:0.21526s 运行时间:0.216346s 运行时间:0.218951s 运行时间:0.21587s 运行时间:0.213639s 运行时间:0.219399s 运行时间:0.213403s
具有特征3的普通C++ 运行时间:0.21052s 运行时间:0.220779s 运行时间:0.216269s 运行时间:0.229234s 运行时间:0.212265s 运行时间:0.256714s 运行时间:0.212396s 运行时间:0.248241s 运行时间:0.241537s 运行时间:0.323519s
Python 持续时间:0.23946428298950195 持续时间:0.1663036346435547 持续时间:0.17225909233093262 持续时间: 0.15922021865844727 持续时间: 0.16628384590148926 持续时间: 0.15654635429382324 持续时间: 0.15859222412109375 持续时间: 0.1633443832397461 持续时间:0.1685199737548828
持续时间: 0.16393446922302246
我想在上面的评论中添加一些假设。
一个是numpy正在做多线程。您的C++是使用 -O3 编译的,这通常已经提供了良好的加速。我假设 numpy 不是在默认的 PyPI 包中使用 -O3 或其他优化编译的。然而,它的速度要快得多。发生这种情况的一种方法是,如果它开始时很慢,但使用了多个 CPU 内核。
一种检查方法是通过设置此处提到的变量使其仅使用一个线程:
OMP_NUM_THREADS=1 MPI_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1
或者,或者与上述同时,也可能是由于优化的构建,例如您可以从 Anaconda 安装的 MKL 构建。正如上面的注释所建议的,您还可以看到在C++代码中使用 SSE 或 AVX 在多大程度上提高了其性能,使用-march=native
等编译器标志。