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
的两倍,如果我们充分利用硬件,这是您所期望的。然而,整数情况的时间似乎在 int8
到 int64
之间是恒定的。这一点,连同 5 倍的减速让我怀疑它完全没有使用 SSE。
对于int32
,当a+b
被a & 0xff
或a >> 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
所以在这台机器上,int32
和 float32
之间没有五倍的差异。 float32
和 float64
.
之间也没有因数二
从处理器利用率我可以看出 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 中。
考虑以下 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
的两倍,如果我们充分利用硬件,这是您所期望的。然而,整数情况的时间似乎在 int8
到 int64
之间是恒定的。这一点,连同 5 倍的减速让我怀疑它完全没有使用 SSE。
对于int32
,当a+b
被a & 0xff
或a >> 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
所以在这台机器上,int32
和 float32
之间没有五倍的差异。 float32
和 float64
.
从处理器利用率我可以看出 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 中。