考虑以下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
,这是您在充分利用硬件时所期望的。然而,对于int8
到int64
,整数情况的时间似乎是恒定的。这与5倍的减速一起使我怀疑它完全没有使用SSE。
对于int32
,当a+b
被a & 0xff
或a >> 2
替换时,我观察到类似的20μs值,表明问题不仅限于添加。
我正在使用numpy 1.9.1
,但不幸的是我不记得我是在本地编译还是下载了二进制文件。但无论哪种方式,这种表现观察对我来说都是令人震惊的。我的版本怎么可能在整数运算中如此无望?
编辑:我也在一台类似的但是单独的PC上进行了测试,运行numpy 1.8
,我很确定这是直接来自PythonXY二进制文件。我得到了相同的结果。
问题:其他人是否会看到类似的结果,如果不是我能做些什么呢?
答案 0 :(得分:2)
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
所以在这台机器上,int32
和float32
之间没有五个因素的区别。 float32
和float64
之间也不存在两个因子。
从处理器利用率我可以看到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,因为它在我的机器上明显更快。