Python Numpy:np.int32比np.float64“慢”

时间:2013-09-11 14:03:49

标签: python numpy floating-point int32

我想了解python的奇怪行为。 让我们考虑一个形状为M的矩阵6000 x 2000。该矩阵用有符号整数填充。我想要计算np.transpose(M)*M。两个选项:

  • 当我“自然地”(即没有指定任何打字)时,numpy选择类型np.int32,操作大约需要150秒。
  • 当我强制类型为np.float64时(使用dtype=...),同样的操作大约需要2秒。

我们如何解释这种行为?我天真地认为int乘法比浮点乘法便宜。

非常感谢你的帮助。

2 个答案:

答案 0 :(得分:7)

不,整数倍数并不便宜。但稍后会有更多内容。 最有可能(我99%肯定)numpy在毯子下调用BLAS例程,这可以达到峰值CPU性能的90%。 int矩阵乘法没有特殊规定,很可能是用Python而不是机器编译的版本 - 我实际上是错的,见下文。

关于int vs float速度:在大多数架构(Intel)上它们大致相同,每条指令大约3-5个周期左右,都有串行(X87)和向量( XMM)版本。在Sandy网桥上,PMUL***(整数向量乘法)是5个周期,MULP*(浮动乘法)也是如此。使用Sandy Bridge,您还可以使用256位SIMD向量操作(YMM) - 每条指令可获得8 float个操作 - 我不确定是否存在int对应操作。

这是一个很好的参考:http://www.agner.org/optimize/instruction_tables.pdf

也就是说,指令延迟并不能解释75倍的速度差异。它可能是优化的BLAS(可能是线程)和int32在Python中处理而不是C / Fortran的组合。

我描述了以下代码段:

>>> F = (np.random.random((6000,2000))+4)
>>> I = F.astype(np.int32)
>>> np.dot(F, F.transpose()); np.dot(I, I.transpose())

这就是oprofile所说的:

CPU_CLK_UNHALT...|
  samples|      %|
------------------
  2076880 51.5705 multiarray.so
  1928787 47.8933 libblas.so.3.0

然而,libblas是未经优化的串行Netlib Blas。有了良好的BLAS实现,47%将会低得多,特别是如果它是线程的。

编辑:似乎numpy 提供整数矩阵乘法的编译版本。

答案 1 :(得分:5)

我通过实验发现的一些补充信息。

这可以规避。计时是针对BLAS的intel mkl的intel cpu。我还使用fortran有序数组来保持所有等价物scipy.linalg.blas是fortran BLAS。

让我们看看以下例子:

from scipy.linalg.blas import sgemm
from scipy.linalg.blas import dgemm

arr_int64 = np.random.randint(-500,500,(6000,2000))

arr_int32 = array_int64.astype(np.int32)

arr_float64 = array_int64.astype(np.float64)+np.random.rand(6000,2000)

arr_float32 = array_int64.astype(np.float32)

首先让我们进行DGEMM调用。

%timeit np.dot(arr_float64.T,arr_float64) #400% CPU threaded BLAS
1 loops, best of 3: 969 ms per loop

%timeit np.dot(arr_float32.T,arr_float32) #400% CPU threaded BLAS
1 loops, best of 3: 513 ms per loop

%timeit np.dot(arr_int64.T,arr_int64)     #100% CPU?
1 loops, best of 3: 24.7 s per loop

%timeit np.dot(arr_int32.T,arr_int32)     #100% CPU?
1 loops, best of 3: 21.3 s per loop

直接致电DGEMM / SGEMM:

%timeit dgemm(alpha=1, a=arr_float64, b=arr_float64, trans_a=True)
1 loops, best of 3: 1.13 s per loop

%timeit dgemm(alpha=1, a=arr_int64, b=arr_int64, trans_a=True)
1 loops, best of 3: 869 ms per loop

%timeit sgemm(alpha=1, a=arr_float32, b=arr_float32, trans_a=True)
1 loops, best of 3: 657 ms per loop

%timeit sgemm(alpha=1, a=arr_int32, b=arr_int32, trans_a=True)
1 loops, best of 3: 432 ms per loop

np.allclose( np.dot(arr_int32.T,arr_int32), sgemm(alpha=1, a=arr_int32, b=arr_int32, trans_a=True))
#True

np.dot电话中看起来很奇怪。与天真算法速度类似:

%timeit np.einsum('ij,jk',arr_int32.T,arr_int32)
1 loops, best of 3: 14.1 s per loop

%timeit np.einsum('ij,jk',arr_int64.T,arr_int64)
1 loops, best of 3: 26 s per loop