numpy ufunc / arithmetic performance - 不使用SSE的整数?

时间:2015-02-18 19:39:42

标签: python performance numpy vectorization sse

考虑以下iPython perf测试,我们在其中创建一对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

2 个答案:

答案 0 :(得分:2)

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

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

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

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

答案 1 :(得分:0)

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

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

用于编译numpy的编译器也会产生很大的影响;使用像SIMD这样的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的方式也会产生重大影响。 根据{{​​3}}的答案,我可以看到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,因为它在我的机器上明显更快。