我想了解python的奇怪行为。
让我们考虑一个形状为M
的矩阵6000 x 2000
。该矩阵用有符号整数填充。我想要计算np.transpose(M)*M
。两个选项:
np.int32
,操作大约需要150秒。np.float64
时(使用dtype=...
),同样的操作大约需要2秒。我们如何解释这种行为?我天真地认为int乘法比浮点乘法便宜。
非常感谢你的帮助。
答案 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