114

我想编写一个广泛使用 BLAS 和 LAPACK 线性代数功能的程序。由于性能是一个问题,我做了一些基准测试并想知道我采用的方法是否合法。

可以这么说,我有三个参赛者,想用一个简单的矩阵-矩阵乘法来测试他们的表现。参赛者是:

  1. Numpy,仅使用dot.
  2. Python,通过共享对象调用 BLAS 功能。
  3. C++,通过共享对象调用 BLAS 功能。

设想

我为不同的维度实现了矩阵-矩阵乘法ii从 5 运行到 500,增量为 5 和矩阵m1m2设置如下:

m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)

1. 麻木

使用的代码如下所示:

tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))

2. Python,通过共享对象调用BLAS

带功能

_blaslib = ctypes.cdll.LoadLibrary("libblas.so")
def Mul(m1, m2, i, r):

    no_trans = c_char("n")
    n = c_int(i)
    one = c_float(1.0)
    zero = c_float(0.0)

    _blaslib.sgemm_(byref(no_trans), byref(no_trans), byref(n), byref(n), byref(n), 
            byref(one), m1.ctypes.data_as(ctypes.c_void_p), byref(n), 
            m2.ctypes.data_as(ctypes.c_void_p), byref(n), byref(zero), 
            r.ctypes.data_as(ctypes.c_void_p), byref(n))

测试代码如下所示:

r = numpy.zeros((i,i), numpy.float32)
tBlas = timeit.Timer("Mul(m1, m2, i, r)", "import numpy; from __main__ import i, m1, m2, r, Mul")
rBlas.append((i, tBlas.repeat(20, 1)))

3.c++,通过共享对象调用BLAS

现在 c++ 代码自然要长一些,所以我将信息减少到最低限度。
我用

void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");

我这样测量时间gettimeofday

gettimeofday(&start, NULL);
f(&no_trans, &no_trans, &dim, &dim, &dim, &one, A, &dim, B, &dim, &zero, Return, &dim);
gettimeofday(&end, NULL);
dTimes[j] = CalcTime(start, end);

哪里j是一个循环运行 20 次。我计算过去的时间

double CalcTime(timeval start, timeval end)
{
double factor = 1000000;
return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor;
}

结果

结果如下图所示:

在此处输入图像描述

问题

  1. 你认为我的方法是公平的,还是我可以避免一些不必要的开销?
  2. 您是否期望结果会显示 c++ 和 python 方法之间存在如此巨大的差异?两者都使用共享对象进行计算。
  3. 由于我更愿意在我的程序中使用 python,所以在调用 BLAS 或 LAPACK 例程时我可以做些什么来提高性能?

下载

完整的基准测试可以在这里下载。(JF Sebastian 使该链接成为可能^^)

4

5 回答 5

76

更新(2014 年 7 月 30 日):

我在我们的新 HPC 上重新运行基准测试。硬件和软件堆栈都与原始答案中的设置不同。

我将结果放在谷歌电子表格中(还包含原始答案的结果)。

硬件

我们的 HPC 有两个不同的节点,一个使用 Intel Sandy Bridge CPU,一个使用更新的 Ivy Bridge CPU:

桑迪(MKL、OpenBLAS、ATLAS):

  • CPU:2 x 16 Intel(R) Xeon(R) E2560 Sandy Bridge @ 2.00GHz(16 核)
  • 内存:64 GB

常春藤(MKL、OpenBLAS、ATLAS):

  • CPU : 2 x 20 Intel(R) Xeon(R) E2680 V2 Ivy Bridge @ 2.80GHz (20 Cores, with HT = 40 Cores)
  • 内存:256 GB

软件

软件堆栈适用于 sam 的两个节点。代替GotoBLAS2,使用OpenBLAS并且还有一个设置为 8 个线程(硬编码)的多线程 ATLAS BLAS。

  • 操作系统:苏斯
  • 英特尔编译器:ictce-5.3.0
  • 麻木: 1.8.0
  • OpenBLAS: 0.2.6
  • 地图集: : 3.8.4

点积基准

基准代码与以下相同。但是对于新机器,我还运行了矩阵大小50008000的基准测试。
下表包括原始答案的基准测试结果(重命名为:MKL --> Nehalem MKL、Netlib Blas --> Nehalem Netlib BLAS 等)

矩阵乘法(大小=[1000,2000,3000,5000,8000])

单线程性能: 单线程性能

多线程性能(8 线程): 多线程(8 线程)性能

线程与矩阵大小(Ivy Bridge MKL)矩阵大小与线程

基准套件

基准套件

单线程性能: 在此处输入图像描述

多线程(8 线程)性能: 在此处输入图像描述

结论

新的基准测试结果与原始答案中的结果相似。OpenBLASMKL在同一水平上执行,除了特征值测试。特征值测试仅在单线程模式下OpenBLAS上执行得相当好。在多线程模式下,性能更差。

矩阵大小与线程图表”还显示,尽管 MKL 和 OpenBLAS 通常可以很好地扩展内核/线程数,但这取决于矩阵的大小。对于小型矩阵,添加更多内核不会大大提高性能。

从Sandy BridgeIvy Bridge的性能也提高了大约 30% ,这可能是由于更高的时钟速率(+ 0.8 Ghz)和/或更好的架构。


原始答案(04.10.2011):

前段时间我不得不优化一些使用 numpy 和 BLAS 用 python 编写的线性代数计算/算法,所以我对不同的 numpy/BLAS 配置进行了基准测试/测试。

具体来说,我测试了:

  • Numpy 与 ATLAS
  • 带有GotoBlas2的 Numpy (1.13)
  • 带 MKL 的 Numpy (11.1/073)
  • 带有加速框架的 Numpy (Mac OS X)

我确实运行了两个不同的基准测试:

  1. 不同大小矩阵的简单点积
  2. 基准套件,可在此处找到。

这是我的结果:

机器

Linux(MKL、ATLAS、No-MKL、GotoBlas2):

  • 操作系统:Ubuntu Lucid 10.4 64 位。
  • CPU:2 x 4 Intel(R) Xeon(R) E5504 @ 2.00GHz(8 核)
  • 内存:24 GB
  • 英特尔编译器:11.1/073
  • 西比:0.8
  • 麻木:1.5

Mac Book Pro(加速框架):

  • 操作系统:Mac OS X Snow Leopard (10.6)
  • CPU : 1 Intel Core 2 Duo 2.93 Ghz (2 Cores)
  • 内存:4 GB
  • 西比:0.7
  • 麻木:1.3

Mac 服务器(加速框架):

  • 操作系统:Mac OS X Snow Leopard Server (10.6)
  • CPU:4 X Intel(R) Xeon(R) E5520 @ 2.26 Ghz(8 核)
  • 内存:4 GB
  • 西比:0.8
  • 麻木:1.5.1

点积基准

代码

import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)

结果

    系统 | 大小 = 1000 | 大小 = 2000 | 大小 = 3000 |
netlib BLAS | 1350 毫秒 | 10900 毫秒 | 39200 毫秒 |    
ATLAS (1 CPU) | 314 毫秒 | 2560 毫秒 | 8700 毫秒 |     
MKL(1 个 CPU)| 268 毫秒 | 2110 毫秒 | 7120 毫秒 |
MKL(2 个 CPU)| - | - | 3660 毫秒 |
MKL(8 个 CPU)| 39 毫秒 | 319 毫秒 | 1000 毫秒 |
GotoBlas2 (1 CPU) | 266 毫秒 | 2100 毫秒 | 7280 毫秒 |
GotoBlas2(2 个 CPU)| 139 毫秒 | 1009 毫秒 | 3690 毫秒 |
GotoBlas2(8 个 CPU)| 54 毫秒 | 389 毫秒 | 1250 毫秒 |
Mac OS X (1 CPU) | 143 毫秒 | 1060 毫秒 | 3605 毫秒 |
Mac 服务器(1 个 CPU)| 92 毫秒 | 714 毫秒 | 2130 毫秒 |

点积基准 - 图表

基准套件

代码
有关基准套件的更多信息,请参见此处

结果

    系统 | 特征值 | svd | 检测 | 库存 | 点 |
netlib BLAS | 1688 毫秒 | 13102 毫秒 | 438 毫秒 | 2155 毫秒 | 3522 毫秒 |
ATLAS (1 CPU) | 1210 毫秒 | 5897 毫秒 | 170 毫秒 | 560 毫秒 | 893 毫秒 |
MKL(1 个 CPU)| 691 毫秒 | 4475 毫秒 | 141 毫秒 | 450 毫秒 | 736 毫秒 |
MKL(2 个 CPU)| 552 毫秒 | 2718 毫秒 | 96 毫秒 | 267 毫秒 | 423 毫秒 |
MKL(8 个 CPU)| 525 毫秒 | 1679 毫秒 | 60 毫秒 | 137 毫秒 | 197 毫秒 |  
GotoBlas2 (1 CPU) | 2124 毫秒 | 4636 毫秒 | 147 毫秒 | 456 毫秒 | 743 毫秒 |
GotoBlas2(2 个 CPU)| 1560 毫秒 | 3278 毫秒 | 116 毫秒 | 295 毫秒 | 460 毫秒 |
GotoBlas2(8 个 CPU)| 741 毫秒 | 2914 毫秒 | 82 毫秒 | 262 毫秒 | 192 毫秒 |
Mac OS X (1 CPU) | 948 毫秒 | 4339 毫秒 | 151 毫秒 | 318 毫秒 | 566 毫秒 |
Mac 服务器(1 个 CPU)| 1033 毫秒 | 3645 毫秒 | 99 毫秒 | 232 毫秒 | 342 毫秒 |

基准套件 - 图表

安装

MKL的安装包括安装完整的英特尔编译器套件,这非常简单。然而,由于一些错误/问题,使用 MKL 支持配置和编译 numpy 有点麻烦。

GotoBlas2是一个可以轻松编译为共享库的小包。但是,由于存在错误,您必须在构建共享库后重新创建它才能与 numpy 一起使用。
除了为多个目标平台构建它之外,由于某种原因,它无法正常工作。所以我必须为每个平台创建一个.so文件,我想为其优化libgoto2.so文件。

如果您从 Ubuntu 的存储库安装 numpy,它将自动安装和配置 numpy 以使用ATLAS。从源代码安装ATLAS可能需要一些时间,并且需要一些额外的步骤(fortran 等)。

如果您在带有FinkMac Ports的 Mac OS X 机器上安装 numpy,它将配置 numpy 以使用ATLASApple 的 Accelerate Framework。您可以通过在 numpy.core._dotblas文件上运行 ldd 或调用numpy.show_config()来检查。

结论

MKL表现最好,紧随其后的是GotoBlas2
特征值测试中,GotoBlas2 的表现出乎意料地差于预期。不知道为什么会这样。
Apple 的 Accelerate Framework性能非常好,尤其是在单线程模式下(与其他 BLAS 实现相比)。

GotoBlas2和MKL都可以很好地扩展线程数。因此,如果您必须处理在多个线程上运行它的大型矩阵,将会有很大帮助。

在任何情况下都不要使用默认的netlib blas实现,因为它对于任何严肃的计算工作来说都太慢了。

在我们的集群上,我还安装了AMD 的 ACML,性能类似于MKLGotoBlas2。我没有任何数字很难。

我个人会推荐使用GotoBlas2,因为它更容易安装并且是免费的。

如果你想用 C++/C 编写代码,还可以查看Eigen3 ,它在某些情况下应该优于MKL/GotoBlas2 ,而且也很容易使用。

于 2011-10-04T09:33:50.307 回答
62

我已经运行了你的基准测试。我的机器上的 C++ 和 numpy 没有区别:

沃尔坦的基准

你认为我的方法是公平的,还是我可以避免一些不必要的开销?

这似乎是公平的,因为结果没有差异。

您是否期望结果会显示 c++ 和 python 方法之间存在如此巨大的差异?两者都使用共享对象进行计算。

不。

由于我更愿意在我的程序中使用 python,所以在调用 BLAS 或 LAPACK 例程时我可以做些什么来提高性能?

确保 numpy 在您的系统上使用优化版本的 BLAS/LAPACK 库。

于 2011-09-30T18:00:19.917 回答
20

这是另一个基准测试(在 Linux 上,只需键入make):http ://dl.dropbox.com/u/5453551/blas_call_benchmark.zip

http://dl.dropbox.com/u/5453551/blas_call_benchmark.png

我看不出大型矩阵的不同方法之间、Numpy、Ctypes 和 Fortran 之间的任何区别。(Fortran 而不是 C++ --- 如果这很重要,那么您的基准可能会被破坏。)

CalcTime在 C++ 中的函数似乎有符号错误。... + ((double)start.tv_usec))应该改为... - ((double)start.tv_usec))也许您的基准测试还存在其他错误,例如,在不同的 BLAS 库之间进行比较,或者在不同的 BLAS 设置(如线程数)之间进行比较,或者在实时时间和 CPU 时间之间进行比较?

编辑:未能计算CalcTime函数中的大括号 - 没关系。

作为指导:如果您进行基准测试,请始终将所有代码发布到某处。在没有完整代码的情况下对基准进行评论,尤其是在令人惊讶的时候,通常是没有成效的。


要找出链接到哪个 BLAS Numpy,请执行以下操作:

$蟒蛇
Python 2.7.2+(默认,2011 年 8 月 16 日,07:24:41)
[GCC 4.6.1] 在 linux2 上
输入“帮助”、“版权”、“信用”或“许可”以获取更多信息。
>>> 导入 numpy.core._dotblas
>>> numpy.core._dotblas.__file__
'/usr/lib/pymodules/python2.7/numpy/core/_dotblas.so'
>>>
$ ldd /usr/lib/pymodules/python2.7/numpy/core/_dotblas.so
    linux-vdso.so.1 => (0x00007fff5ebff000)
    libblas.so.3gf => /usr/lib/libblas.so.3gf (0x00007fbe618b3000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe61514000)

更新:如果您无法导入 numpy.core._dotblas,您的 Numpy 正在使用其内部的 BLAS 后备副本,该副本速度较慢,并且不适合用于性能计算!下面@Woltan 的回复表明这是他/她在 Numpy 与 Ctypes+BLAS 中看到的差异的解释。

要解决这种情况,您需要 ATLAS 或 MKL --- 查看这些说明:http ://scipy.org/Installing_SciPy/Linux 大多数 Linux 发行版都附带 ATLAS,因此最好的选择是安装它们的libatlas-dev软件包(名称可能会有所不同) .

于 2011-09-29T18:04:53.910 回答
9

鉴于您在分析中表现出的严谨性,我对迄今为止的结果感到惊讶。我将此作为“答案”,但这只是因为评论太长并且确实提供了一种可能性(尽管我希望您已经考虑过)。

我会认为 numpy/python 方法不会为合理复杂的矩阵增加太多开销,因为随着复杂性的增加,python 参与的比例应该很小。我对图表右侧的结果更感兴趣,但是显示的数量级差异会令人不安。

我想知道您是否正在使用 numpy 可以利用的最佳算法。来自 linux 的编译指南:

“构建 FFTW (3.1.2):SciPy 版本 >= 0.7 和 Numpy >= 1.2:由于许可证、配置和维护问题,在 SciPy >= 0.7 和 NumPy >= 1.2 的版本中删除了对 FFTW 的支持。现在改为使用fftpack 的内置版本。如果分析需要,有几种方法可以利用 FFTW 的速度。降级到包含支持的 Numpy/Scipy 版本。安装或创建自己的 FFTW 包装器。请参阅http: //developer.berlios.de/projects/pyfftw/作为未经认可的示例。”

你用 mkl 编译过 numpy 吗?(http://software.intel.com/en-us/articles/intel-mkl/)。如果您在 linux 上运行,使用 mkl 编译 numpy 的说明在这里:http ://www.scipy.org/Installing_SciPy/Linux#head-7ce43956a69ec51c6f2cedd894a4715d5bfff974 (尽管有 url)。关键部分是:

[mkl]
library_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/lib/intel64
include_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/include
mkl_libs = mkl_intel_lp64,mkl_intel_thread,mkl_core 

如果您在 Windows 上,您可以使用 mkl 获得编译后的二进制文件,(还可以获得 pyfftw 和许多其他相关算法),网址为:http ://www.lfd.uci.edu/~gohlke/pythonlibs/,带有感谢加州大学欧文分校荧光动力学实验室的 Christoph Gohlke。

警告,在任何一种情况下,都有许多许可问题等需要注意,但英特尔页面解释了这些问题。同样,我想您已经考虑过这一点,但是如果您满足许可要求(在 linux 上很容易做到),与使用简单的自动构建相比,这将大大加快 numpy 部分的速度,甚至无需 FFTW。我有兴趣关注这个帖子,看看其他人的想法。无论如何,非常严谨和出色的问题。感谢您发布它。

于 2011-09-29T12:09:48.137 回答
0

让我贡献一个有点奇怪的发现 结果

Mynumpy链接到mkl,由 numpy.show_config() 给出。我不知道使用了哪种libblas.soC++/BLAS。我希望有人能告诉我一种解决方法。

我认为结果很大程度上取决于所使用的库。我无法隔离 C++/BLAS 的效率。

于 2021-12-18T02:04:40.520 回答