numpy ufunc/arithmetic 性能 - 整数不使用 SSE?

numpy ufunc/arithmetic performance - integer not using SSE?

考虑以下 iPython 性能测试,我们在其中创建一对 10,000 长 32 位向量并将它们相加。先用整数运算再用浮点数运算:

from numpy.random import randint
from numpy import int32, float32

a, b = randint(255,size=10000).astype(int32), randint(255,size=10000).astype(int32)
%timeit a+b  # int32 addition, gives 20.6µs per loop

a, b = randint(255,size=10000).astype(float32), randint(255,size=10000).astype(float32)
%timeit a+b  # float32 addition, gives 3.91µs per loop

为什么浮点版本快 5 倍?

如果您使用 float64 进行相同的测试,则花费的时间是 float32 的两倍,如果我们充分利用硬件,这是您所期望的。然而,整数情况的时间似乎在 int8int64 之间是恒定的。这一点,连同 5 倍的减速让我怀疑它完全没有使用 SSE。

对于int32,当a+ba & 0xffa >> 2替换时,我观察到类似的20µs值,表明问题不仅限于加法。

我正在使用 numpy 1.9.1,但不幸的是我不记得我是在本地编译还是下载二进制文件。但无论哪种方式,这种性能观察都让我非常震惊。我的版本怎么可能在整数运算上如此无望?

编辑: 我也在一台类似但独立的 PC 上进行了测试,运行 numpy 1.8,我相当确定它是直接来自PythonXY 二进制文件。我得到了相同的结果。

问题:其他人是否看到了类似的结果,如果没有,我该怎么做才能像他们一样?

更新: 我创建了 a new issue on numpy's github repo.

在现代 CPU 上,有很多因素会影响性能。数据是整数还是浮点数只是其中之一。

诸如数据是在缓存中还是必须从 RAM 中获取(或者更糟糕的是从交换区中获取)等因素都会产生很大的影响。

用于编译numpy的编译器也会有很大的影响;使用 SIMD instructions like SSE 有多好?这些可以显着加快数组操作。

我的系统(Intel Core2 Quad Q9300)的结果;

In [1]: from numpy.random import randint

In [2]: from numpy import int32, float32, float64

In [3]: a, b = randint(255,size=10000).astype(int32), randint(255,size=10000).astype(int32)

In [4]: %timeit a+b
100000 loops, best of 3: 12.9 µs per loop

In [5]: a, b = randint(255,size=10000).astype(float32), randint(255,size=10000).astype(float32)

In [6]: %timeit a+b
100000 loops, best of 3: 8.25 µs per loop

In [7]: a, b = randint(255,size=10000).astype(float64), randint(255,size=10000).astype(float64)

In [8]: %timeit a+b
100000 loops, best of 3: 13.9 µs per loop

所以在这台机器上,int32float32 之间没有五倍的差异。 float32float64.

之间也没有因数二

从处理器利用率我可以看出 timeit 循环仅使用四个可用内核之一。 这似乎证实了这些简单的操作不使用 BLAS 例程,因为这个 numpy 是用并行 openBLAS 构建的。

numpy 的编译方式也会产生重大影响。 根据 this question 的答案,我可以看到使用 objdump 我的 numpy 使用 SSE2 指令和 xmm 寄存器。

In [9]: from numpy import show_config

In [10]: show_config()
atlas_threads_info:
    library_dirs = ['/usr/local/lib']
    language = f77
    include_dirs = ['/usr/local/include']
    define_macros = [('ATLAS_INFO', '"\"None\""')]
    libraries = ['alapack', 'ptf77blas', 'ptcblas', 'atlas']
openblas_lapack_info:
  NOT AVAILABLE
blas_opt_info:
    library_dirs = ['/usr/local/lib']
    language = f77
    libraries = ['openblasp', 'openblasp']
mkl_info:
  NOT AVAILABLE
lapack_mkl_info:
  NOT AVAILABLE
lapack_opt_info:
    library_dirs = ['/usr/local/lib']
    language = f77
    include_dirs = ['/usr/local/include']
    define_macros = [('ATLAS_INFO', '"\"None\""')]
    libraries = ['alapack', 'ptf77blas', 'ptcblas', 'atlas']
openblas_info:
    library_dirs = ['/usr/local/lib']
    language = f77
    libraries = ['openblasp', 'openblasp']
blas_mkl_info:
  NOT AVAILABLE

如果你想看看你使用的BLAS的效果,运行下面的程序用不同的BLAS库编译的numpy。

from __future__ import print_function
import numpy
import sys
import timeit

try:
    import numpy.core._dotblas
    print('FAST BLAS')
except ImportError:
    print('slow blas')

print("version:", numpy.__version__)
print("maxint:", sys.maxsize)
print()

setup = "import numpy; x = numpy.random.random((1000,1000))"
count = 5

t = timeit.Timer("numpy.dot(x, x.T)", setup=setup)
print("dot:", t.timeit(count)/count, "sec")

在我的机器上得到;

FAST BLAS
version: 1.9.1
maxint: 9223372036854775807

dot: 0.06626860399264842 sec

根据这个测试的结果,我从 ATLAS 切换到 OpenBLAS,因为它在我的机器上速度明显更快。

如果编译器支持,尚未发布的 numpy 1.10 也将向量化整数运算。 在此更改中添加了它: https://github.com/numpy/numpy/pull/5144

例如使用 gcc 4.8 编译的当前 git head 的测试用例导致 int 和 float 的速度相同,并且生成的代码看起来不错:

  0.04 │27b:   movdqu (%rdx,%rax,1),%xmm0
 25.33 │       add    [=10=]x1,%r10
       │       movdqu (%r8,%rax,1),%xmm1
       │       paddd  %xmm1,%xmm0
 23.17 │       movups %xmm0,(%rcx,%rax,1)
 34.72 │       add    [=10=]x10,%rax
 16.05 │       cmp    %r10,%rsi
       │     ↑ ja     27b

如果 cpu 支持(例如 intel haswell),可以使用 AVX2 存档额外的加速,尽管目前需要通过 OPT="-O3 -mavx2" 编译来完成,没有运行时检测这还在 numpy 中。