Linux和Windows之间的numpy性能差异

8

我正在尝试在2台不同的计算机上运行sklearn.decomposition.TruncatedSVD()并了解性能差异。

计算机1(Windows 7,物理计算机)

OS Name Microsoft Windows 7 Professional
System Type x64-based PC
Processor   Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz, 3401 Mhz, 4 Core(s), 
8 Logical Installed Physical Memory (RAM)   8.00 GB
Total Physical Memory   7.89 GB

计算机2 (在亚马逊云上运行的Debian操作系统)

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8

width: 64 bits
capabilities: ldt16 vsyscall32
*-core
   description: Motherboard
   physical id: 0
*-memory
   description: System memory
   physical id: 0
   size: 29GiB
*-cpu
   product: Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
   vendor: Intel Corp.
   physical id: 1
   bus info: cpu@0
   width: 64 bits

计算机 3(Windows 2008R2,位于亚马逊云上)

OS Name Microsoft Windows Server 2008 R2 Datacenter
Version 6.1.7601 Service Pack 1 Build 7601
System Type x64-based PC
Processor   Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz, 2500 Mhz, 
4 Core(s), 8 Logical Processor(s)
Installed Physical Memory (RAM) 30.0 GB

两台计算机都使用Python 3.2和相同版本的sklearn、numpy、scipy。

我按照如下方式运行了cProfile

print(vectors.shape)
>>> (7500, 2042)

_decomp = TruncatedSVD(n_components=680, random_state=1)
global _o
_o = _decomp
cProfile.runctx('_o.fit_transform(vectors)', globals(), locals(), sort=1)

计算机 1 输出

>>>    833 function calls in 1.710 seconds
Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.767    0.767    0.782    0.782 decomp_svd.py:15(svd)
    1    0.249    0.249    0.249    0.249 {method 'enable' of '_lsprof.Profiler' objects}
    1    0.183    0.183    0.183    0.183 {method 'normal' of 'mtrand.RandomState' objects}
    6    0.174    0.029    0.174    0.029 {built-in method csr_matvecs}
    6    0.123    0.021    0.123    0.021 {built-in method csc_matvecs}
    2    0.110    0.055    0.110    0.055 decomp_qr.py:14(safecall)
    1    0.035    0.035    0.035    0.035 {built-in method dot}
    1    0.020    0.020    0.589    0.589 extmath.py:185(randomized_range_finder)
    2    0.018    0.009    0.019    0.010 function_base.py:532(asarray_chkfinite)
   24    0.014    0.001    0.014    0.001 {method 'ravel' of 'numpy.ndarray' objects}
    1    0.007    0.007    0.009    0.009 twodim_base.py:427(triu)
    1    0.004    0.004    1.710    1.710 extmath.py:232(randomized_svd)

计算机2输出

>>>    858 function calls in 40.145 seconds
Ordered by: internal time
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    2   32.116   16.058   32.116   16.058 {built-in method dot}
    1    6.148    6.148    6.156    6.156 decomp_svd.py:15(svd)
    2    0.561    0.281    0.561    0.281 decomp_qr.py:14(safecall)
    6    0.561    0.093    0.561    0.093 {built-in method csr_matvecs}
    1    0.337    0.337    0.337    0.337 {method 'normal' of 'mtrand.RandomState' objects}
    6    0.202    0.034    0.202    0.034 {built-in method csc_matvecs}
    1    0.052    0.052    1.633    1.633 extmath.py:183(randomized_range_finder)
    1    0.045    0.045    0.054    0.054 _methods.py:73(_var)
    1    0.023    0.023    0.023    0.023 {method 'argmax' of 'numpy.ndarray' objects}
    1    0.023    0.023    0.046    0.046 extmath.py:531(svd_flip)
    1    0.016    0.016   40.145   40.145 <string>:1(<module>)
   24    0.011    0.000    0.011    0.000 {method 'ravel' of 'numpy.ndarray' objects}
    6    0.009    0.002    0.009    0.002 {method 'reduce' of 'numpy.ufunc' objects}
    2    0.008    0.004    0.009    0.004 function_base.py:532(asarray_chkfinite)

计算机3的输出

>>>         858 function calls in 2.223 seconds
Ordered by: internal time
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.956    0.956    0.972    0.972 decomp_svd.py:15(svd)
    2    0.306    0.153    0.306    0.153 {built-in method dot}
    1    0.274    0.274    0.274    0.274 {method 'normal' of 'mtrand.RandomState' objects}
    6    0.205    0.034    0.205    0.034 {built-in method csr_matvecs}
    6    0.151    0.025    0.151    0.025 {built-in method csc_matvecs}
    2    0.133    0.067    0.133    0.067 decomp_qr.py:14(safecall)
    1    0.032    0.032    0.043    0.043 _methods.py:73(_var)
    1    0.030    0.030    0.030    0.030 {method 'argmax' of 'numpy.ndarray' objects}
   24    0.026    0.001    0.026    0.001 {method 'ravel' of 'numpy.ndarray' objects}
    2    0.019    0.010    0.020    0.010 function_base.py:532(asarray_chkfinite)
    1    0.019    0.019    0.773    0.773 extmath.py:183(randomized_range_finder)
    1    0.019    0.019    0.049    0.049 extmath.py:531(svd_flip)

注意从0.035秒/调用到16.058秒/调用的{内置方法点}巨大差异,慢了450倍!!

------+---------+---------+---------+---------+---------------------------------------
ncalls| tottime | percall | cumtime | percall | filename:lineno(function)  HARDWARE
------+---------+---------+---------+---------+---------------------------------------
1     |  0.035  |  0.035  |  0.035  |  0.035  | {built-in method dot}      Computer 1
2     | 32.116  | 16.058  | 32.116  | 16.058  | {built-in method dot}      Computer 2
2     |  0.306  |  0.153  |  0.306  |  0.153  | {built-in method dot}      Computer 3

我知道性能应该有差异,但是它应该那么高吗?

有没有办法进一步调试这个性能问题?

编辑

我测试了一个新的计算机,计算机 3 的硬件与计算机 2 类似,但操作系统不同。

结果是 {内置方法 dot} 的速度为每次调用 0.153 秒,仍然比 Linux 快 100 倍!

编辑 2

计算机 1 的 numpy 配置

>>> np.__config__.show()
lapack_opt_info:
    libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd', 'mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd']
    library_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/lib/intel64']
    define_macros = [('SCIPY_MKL_H', None)]
    include_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/include']
blas_opt_info:
    libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd']
    library_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/lib/intel64']
    define_macros = [('SCIPY_MKL_H', None)]
    include_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/include']
openblas_info:
  NOT AVAILABLE
lapack_mkl_info:
    libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd', 'mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd']
    library_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/lib/intel64']
    define_macros = [('SCIPY_MKL_H', None)]
    include_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/include']
blas_mkl_info:
    libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd']
    library_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/lib/intel64']
    define_macros = [('SCIPY_MKL_H', None)]
    include_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/include']
mkl_info:
    libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_intel_lp64', 'mkl_intel_thread', 'mkl_core', 'libiomp5md', 'libifportmd']
    library_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/lib/intel64']
    define_macros = [('SCIPY_MKL_H', None)]
    include_dirs = ['C:/Program Files (x86)/Intel/Composer XE/mkl/include']

计算机到Numpy配置

>>> np.__config__.show()
lapack_info:
  NOT AVAILABLE
lapack_opt_info:
  NOT AVAILABLE
blas_info:
    libraries = ['blas']
    library_dirs = ['/usr/lib']
    language = f77
atlas_threads_info:
  NOT AVAILABLE
atlas_blas_info:
  NOT AVAILABLE
lapack_src_info:
  NOT AVAILABLE
openblas_info:
  NOT AVAILABLE
atlas_blas_threads_info:
  NOT AVAILABLE
blas_mkl_info:
  NOT AVAILABLE
blas_opt_info:
    libraries = ['blas']
    library_dirs = ['/usr/lib']
    language = f77
    define_macros = [('NO_ATLAS_INFO', 1)]
atlas_info:
  NOT AVAILABLE
lapack_mkl_info:
  NOT AVAILABLE
mkl_info:
  NOT AVAILABLE

请提供每个节点的 python --version 输出。 - Brian Cain
3
请参考命令python -c 'import numpy as np; np.__config__.show()'进行比较。 - Brian Cain
5
你是怎么安装numpy的?它使用的是哪个BLAS库?如果Windows系统使用MKL库,而Linux则用默认的BLAS库,那可能会解释很多问题。你可以通过运行"np.config.show()"来获取这些信息。 - Andreas Mueller
@AndreasMueller,根据您的评论,我编辑了我的问题。看起来您是正确的。是否有一份关于如何使用MKL编译numpy的手册? - Ofer Helman
找到了,https://software.intel.com/en-us/articles/numpyscipy-with-intel-mkl - Ofer Helman
1
如果您在Linux上没有访问MKL(它不是免费的),您可以使用OpenBLAS并获得相同的性能。 - Sturla Molden
2个回答

6
{内置方法dot} 是指 np.dot 函数,它是一个 NumPy 对 CBLAS 例程的矩阵-矩阵、矩阵-向量和向量-向量乘法进行了封装。你的 Windows 机器使用的是高度调优过的 Intel MKL 版本的 CBLAS。而 Linux 机器则使用的是旧版本的参考实现,速度较慢。
如果你安装了 ATLAS 或者 OpenBLAS(两者都可通过 Linux 包管理器获得),或者实际上安装了 Intel MKL,你可能会看到巨大的速度提升。试试运行 sudo apt-get install libatlas-dev 命令,检查一遍 NumPy 配置以查看是否已经选择了 ATLAS,并重新测量一下。
一旦你决定了合适的 CBLAS 库,你可能想要重新编译 scikit-learn。它主要使用 NumPy 来满足其线性代数需求,但一些算法(特别是 k-means)直接使用 CBLAS。
操作系统与此无关。

2020年:现在从pip安装的numpy是针对OpenBLAS编译的,除非CPU支持AVX-512指令(OpenBLAS尚未实现),否则其性能与Intel MKL基本相同。来自anaconda的numpy使用Intel MKL。重要的是要知道,在没有覆盖所选代码路径的技巧(MKL_DEBUG_CPU_TYPE=5)的情况下,Intel MKL在AMD Zen-based CPU上运行非常缓慢。 - beginner_

1
注意与0.035s/call相比的内置方法差异,慢了450倍的16.058s/call!!
时钟速度和缓存命中率是需要考虑的两个重要因素。Xeon E5-2670比Core i7-3770拥有更多的缓存。而i7-3770在turbo模式下具有更高的峰值时钟速度。虽然您的Xeon硬件上有大量缓存,在EC2上您可能会与其他客户共享该缓存。
有没有办法进一步调试这个性能问题?
好吧,您有不同的测量(输出)和多个输入(操作系统和硬件)之间的差异。鉴于这些不同的输入,这些不同的输出很可能是预期的。
CPU性能计数器能更好地隔离不同系统上算法性能的影响。至于Xeons,它们有更丰富的性能计数器,但它们都应该有CPU_CLK_UNHALTED和LLC_MISSES。这些计数器通过将指令指针映射到代码执行或缓存未命中等事件来工作。因此,您可以看到代码的哪些部分是CPU或缓存受限的。由于时钟速度和缓存大小在不同的目标之间不同,您可能会发现一个是缓存受限的,而另一个是CPU受限的。
Linux有一个名为perf(有时称为perf_events)的工具。另请参见http://www.brendangregg.com/perf.html 在Linux和Windows上,您也可以使用Intel VTune。

我编辑了我的问题,添加了计算机3以消除不同的硬件问题。 - Ofer Helman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接