编辑:参见this question我在那里学习了如何使用Numba在Python中并行化稀疏矩阵向量乘法,并且能够与Matlab结合使用。
原始问题:
我观察到稀疏矩阵向量乘法在Matlab中比在Python中快4或5倍(使用scipy稀疏矩阵)。以下是Matlab命令行的一些细节:
>> whos A
Name Size Bytes Class Attributes
A 47166x113954 610732376 double sparse
>> whos ATrans
Name Size Bytes Class Attributes
ATrans 113954x47166 610198072 double sparse
>> nnz(A)/numel(A)
ans =
0.0071
>> whos x
Name Size Bytes Class Attributes
x 113954x1 911632 double
>> myFun = @() A*x; timeit(myFun)
ans =
0.0601
>> myFun = @() ATrans'*x; timeit(myFun)
ans =
0.0120
矩阵ATrans是A的转置。请注意,在Matlab中计算A乘x约需0.06秒,但如果我使用奇怪的“转置技巧”并计算ATrans'乘以x(产生与A相同的结果)次x),计算需要0.012秒。 (我不明白为什么这个技巧有效。)
以下是Python命令行的一些计时结果:
In[50]: type(A)
Out[50]:
scipy.sparse.csc.csc_matrix
In[51]: A.shape
Out[51]:
(47166, 113954)
In[52]: type(x)
Out[52]:
numpy.ndarray
In[53]: x.shape
Out[53]:
(113954L, 1L)
In[54]: timeit.timeit('A.dot(x)',setup="from __main__ import A,x",number=100)/100.0
...:
Out[54]:
0.054835451461831324
因此对于相同的矩阵A来说,执行A次x的Python运行时的长度大约是Matlab运行时的4.5倍。如果我以csr格式存储A,那么Python运行时会更糟糕:
In[63]: A = sp.sparse.csr_matrix(A)
In[64]: timeit.timeit('A.dot(x)',setup="from __main__ import A,x",number=100)/100.0
...:
Out[64]:
0.0722580226496575
这里有一些关于哪个python版本以及我正在使用的anaconda版本的信息:
In[2]: import sys; print('Python %s on %s' % (sys.version, sys.platform))
Python 2.7.12 |Anaconda 4.2.0 (64-bit)| (default, Jun 29 2016, 11:07:13) [MSC v.1500 64 bit (AMD64)] on win32
问题:为什么Matlab中的稀疏矩阵向量乘法比Python更快? 我怎样才能在Python中同样快速地实现它?
编辑1:这是一个线索。在Python中,如果我将线程数设置为1,则密集矩阵向量乘法的运行时间会受到严重影响,但稀疏矩阵向量乘法的运行时间几乎没有变化。
In[48]: M = np.random.rand(1000,1000)
In[49]: y = np.random.rand(1000,1)
In[50]: import mkl
In[51]: mkl.get_max_threads()
Out[51]:
20
In[52]: timeit.timeit('M.dot(y)', setup = "from __main__ import M,y", number=100) / 100.0
Out[52]:
7.232593519574948e-05
In[53]: mkl.set_num_threads(1)
In[54]: timeit.timeit('M.dot(y)', setup = "from __main__ import M,y", number=100) / 100.0
Out[54]:
0.00044465965093536396
In[56]: type(A)
Out[56]:
scipy.sparse.csc.csc_matrix
In[57]: timeit.timeit('A.dot(x)', setup = "from __main__ import A,x", number=100) / 100.0
Out[57]:
0.055780856886028685
In[58]: mkl.set_num_threads(20)
In[59]: timeit.timeit('A.dot(x)', setup = "from __main__ import A,x", number=100) / 100.0
Out[59]:
0.05550840215802509
因此,对于密集矩阵向量乘积,将线程数设置为1会使运行时间减少约6倍。但对于稀疏矩阵向量乘积,将线程数减少为1并未改变运行时。
我认为这表明在Python中,稀疏矩阵向量乘法不是并行执行的,而密集矩阵向量乘法则是利用所有可用核心。你同意这个结论吗?如果是这样,有没有办法在Python中利用所有可用内核进行稀疏矩阵向量乘法?
请注意,Anaconda默认使用英特尔MKL优化:https://www.continuum.io/blog/developer-blog/anaconda-25-release-now-mkl-optimizations
编辑2: 我读到here“对于稀疏矩阵,除稀疏三角形求解器之外的所有2级[BLAS]操作都在”英特尔MKL中“。这告诉我,scipy没有使用英特尔MKL来执行稀疏矩阵向量乘法。似乎@hpaulj(在下面发布的答案中)通过检查函数csr_matvec的代码确认了这个结论。那么,我可以直接调用英特尔MKL稀疏矩阵向量乘法函数吗?我该怎么做?
编辑3:这是一个额外的证据。当我将最大线程数设置为1时,Matlab稀疏矩阵向量乘法运算符似乎没有变化。
>> maxNumCompThreads
ans =
20
>> myFun = @() ATrans'*x; timeit(myFun)
ans =
0.012545604076342
>> maxNumCompThreads(1) % set number of threads to 1
ans =
20
>> maxNumCompThreads % Check that max number of threads is 1
ans =
1
>> myFun = @() ATrans'*x; timeit(myFun)
ans =
0.012164191957568
这使我质疑我之前的理论,即Matlab的优势在于多线程。
答案 0 :(得分:3)
根据稀疏和密集的混合,我们得到时间的变化:
In [40]: A = sparse.random(1000,1000,.1, format='csr')
In [41]: x = np.random.random((1000,1))
In [42]: Ad = A.A
In [43]: xs = sparse.csr_matrix(x)
带有sparse
的 dense
会产生密集,但带有sparse
的{{1}}会产生稀疏:
sparse
与替代方案相比,稀疏且密集的外观相当不错:
In [47]: A.dot(xs)
Out[47]:
<1000x1 sparse matrix of type '<class 'numpy.float64'>'
with 1000 stored elements in Compressed Sparse Row format>
In [48]: np.allclose(A.dot(x), Ad.dot(x))
Out[48]: True
In [49]: np.allclose(A.dot(x), A.dot(xs).A)
Out[49]: True
对于更大的矩阵,相对时间可能会有所不同。同样适用于不同的稀疏性。
我无法访问MATLAB,因此无法进行等效测试,但我可以在Octave上尝试此操作。
这是在基本的Linux(ubuntu)计算机上,具有最新的numpy和scipy。
我以前的探索Directly use Intel mkl library on Scipy sparse matrix to calculate A dot A.T with less memory,用矩阵计算处理稀疏矩阵。密集稀疏必须使用不同的代码。我们必须跟踪它以查看它是否委托给基础数学库特殊的东西。
此计算的 In [50]: timeit A.dot(x) # sparse with dense
137 µs ± 269 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [51]: timeit Ad.dot(x) # dense with dense
1.03 ms ± 4.32 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [52]: timeit A.dot(xs) # sparse with sparse
1.44 ms ± 644 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
格式稍慢 - 可能是因为基本迭代是跨行的。
csc
In [80]: Ac = A.tocsc()
In [81]: timeit Ac.dot(x)
216 µs ± 268 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
可以追溯到对编译代码的调用:
A.dot(x)
In [70]: result = np.zeros((1000,1))
In [71]: sparse._sparsetools.csr_matvec(1000,1000,A.indptr, A.indices, A.data, x, result)
是一个_sparsetools
文件,可能是根据Cython(.pyx)代码编译的。
在.so
中sparsetools/csr.h
的代码(模板)是:
matvec
这看起来像void csr_matvec(const I n_row,
const I n_col,
const I Ap[],
const I Aj[],
const T Ax[],
const T Xx[],
T Yx[])
{
for(I i = 0; i < n_row; i++){
T sum = Yx[i];
for(I jj = Ap[i]; jj < Ap[i+1]; jj++){
sum += Ax[jj] * Xx[Aj[jj]];
}
Yx[i] = sum;
}
}
属性(csr
,indptr
),乘法和求和的直接c ++迭代。没有尝试使用优化的数学库或并行核心。
https://github.com/scipy/scipy/blob/master/scipy/sparse/sparsetools/csr.h
答案 1 :(得分:0)
嗯,这取决于你的比较。
基本上MATLAB使用Intel MKL进行线性代数计算(至少大部分是这样)。
在Python中,线性代数计算的后端取决于您使用的包(例如Numpy)和Distributon(例如Anaconda)。
如果你使用Anaconda或Intel's Distribution,Numpy正在使用英特尔MKL,这意味着你应该期待类似的表现。
在其他情况下,这实际上取决于你拥有的东西。