numpy 比 Eigen C++ 更快、更高效?

Wil*_*all 0 c++ numpy compiler-optimization python-3.x eigen3

最近,我和一位同事就 python 和 C++ 的性能比较进行了争论。我们俩主要使用这些语言来进行线性代数。所以我写了两个脚本,一个在 python3 中使用 numpy,另一个在 C++ 中使用 Eigen。

Python3 numpy版本matmul_numpy.py:

import numpy as np
import time
a=np.random.rand(2000,2000)
b=np.random.rand(2000,2000)
start=time.time()
c=a*b
end=time.time()
print(end-start) 
Run Code Online (Sandbox Code Playgroud)

如果我运行这个脚本

python3 matmul_numpy.py
Run Code Online (Sandbox Code Playgroud)

这将返回:

0.07 seconds
Run Code Online (Sandbox Code Playgroud)

C++ 特征版本 matmul_eigen.cpp:


#include <iostream>
#include <Eigen/Dense>
#include "time.h"
int main(){
        clock_t start,end;
        size_t n=2000;
        Eigen::MatrixXd a=Eigen::MatrixXd::Random(n,n);
        Eigen::MatrixXd b=Eigen::MatrixXd::Random(n,n);
        start=clock();
        Eigen::MatrixXd c=a*b;
        end=clock();
        std::cout<<(double)(end-start)/CLOCKS_PER_SEC<<std::endl;
        return 0;}
Run Code Online (Sandbox Code Playgroud)

我编译的方式是

g++ matmul_eigen.cpp -I/usr/include/eigen3 -O3 -march=native -std=c++17 -o matmul_eigen
Run Code Online (Sandbox Code Playgroud)

这将返回(c++11 和 c++17):

0.35 seconds
Run Code Online (Sandbox Code Playgroud)

这对我来说很奇怪,1-为什么 numpy 这里比 C++ 更快?我是否缺少任何其他优化标志?

我想也许是因为 python 解释器在这里执行程序更快。因此,我使用stacks中的该线程使用 cython 编译代码。

编译后的 python 脚本仍然更快(0.11 秒)。这又给我增加了两个问题:

2-为什么变长了?解释器还做了优化吗?

3-为什么Python脚本的二进制文件(37 kb)比c++(57 kb)小?

我将不胜感激任何帮助,

谢谢

Jér*_*ard 13

最大的问题是你正在比较两个完全不同的东西

  • 在 Numpy 中,a*b执行逐元素乘法,因为ab是二维数组,不被视为矩阵。a@b执行矩阵乘法。
  • 在 Eigen 中,a*b执行矩阵乘法,而不是逐元素乘法(请参阅文档)。这是因为ab是矩阵,而不仅仅是二维数组。

两者给出了完全不同的结果。此外,矩阵乘法及时运行O(n**3),而逐元素乘法及时运行O(n**2)。矩阵乘法内核通常是高度优化且受计算限制的。大多数 BLAS 库通常对它们进行并行化。逐元素乘法是受内存限制的(尤其是这里由于页面错误)。因此,矩阵乘法比元素乘法慢也就不足为奇了,而且由于后者受内存限制,差距也不是很大。

在我的 i5-9600KF 处理器(6 核)上,Numpy 需要 9 毫秒a*b(顺序)和 65 毫秒a@b(并行,使用 OpenBLAS)。

注意像这样的 Numpy 按元素乘法不是并行的(至少在 Numpy 的标准默认实现中不是)。Numpy的矩阵乘法使用BLAS库,默认情况下一般是OpenBLAS(这取决于实际的目标平台)。Eigen 还应该使用 BLAS 库,但它可能与 Numpy 的库不同。

另请注意,这clock不是测量并行代码的好方法,因为它不测量挂钟时间,而是测量 CPU 时间(有关更多信息,请参阅这篇文章)。std::chrono::steady_clock通常是 C++ 中更好的选择。


3-为什么Python脚本的二进制文件(37 kb)比c++(57 kb)小?

Python 通常被编译为字节码,而不是本机汇编代码。C++ 通常被编译为可执行程序,其中包含汇编的二进制代码、用于运行程序的附加信息以及元信息。字节码通常非常紧凑,因为它们是更高级别的。本机编译器可以执行优化,使程序变得更大,例如循环展开和内联。CPython(默认的 Python 解释器)不会完成此类优化。事实上,CPython 对字节码没有执行(高级)优化。-Os请注意,您可以使用和等标志告诉 GCC 等本机编译器生成较小的代码(尽管通常较慢)-s