2014-02-07 36 views
1

我有一個問題,我不得不做下面的計算。 我想避免循環版本,所以我矢量化了它。 爲什麼循環版本比矢量化版本更快? 有沒有人對此有過解釋。爲什麼矢量化版本變慢?

THX

import numpy as np 
from numpy.core.umath_tests import inner1d 

num_vertices = 40000 
num_pca_dims = 1000 
num_vert_coords = 3 
a = np.arange(num_vert_coords * num_vertices * num_pca_dims).reshape((num_pca_dims, num_vertices*num_vert_coords)).T 

#n-by-3 
norms = np.arange(num_vertices * num_vert_coords).reshape(num_vertices,-1) 

#Loop version 
def slowversion(a,norms): 
    res_list = [] 
    for c_idx in range(a.shape[1]): 
     curr_col = a[:,c_idx].reshape(-1,3) 
     res = inner1d(curr_col, norms) 
     res_list.append(res) 
    res_list_conc = np.column_stack(res_list) 
    return res_list_conc 


#Fast version 
def fastversion(a,norms): 
    a_3 = a.reshape(num_vertices, 3, num_pca_dims) 
    fast_res = np.sum(a_3 * norms[:,:,None], axis=1) 
    return fast_res 


res_list_conc = slowversion(a,norms) 
fast_res = fastversion(a,norms) 
assert np.all(res_list_conc == fast_res) 
+1

你可以顯示時間嗎? – user2357112

+1

見http://stackoverflow.com/q/14566564/399704看起來有關 –

+0

4000萬雙精度元素= 320 MB。難道你的「快速」版本正在製作整個東西的副本嗎?或者它以非最佳方式訪問元素(不利用緩存一致性)? – Floris

回答

4

你的「慢碼」很可能做的更好,因爲inner1d是一個優化的C++功能,可以*使用您的BLAS實現的。讓我們看一下可比定時進行此項操作:

np.allclose(inner1d(a[:,0].reshape(-1,3), norms), 
      np.sum(a[:,0].reshape(-1,3)*norms,axis=1)) 
True 

%timeit inner1d(a[:,0].reshape(-1,3), norms) 
10000 loops, best of 3: 200 µs per loop 

%timeit np.sum(a[:,0].reshape(-1,3)*norms,axis=1) 
1000 loops, best of 3: 625 µs per loop 

%timeit np.einsum('ij,ij->i',a[:,0].reshape(-1,3), norms) 
1000 loops, best of 3: 325 µs per loop 

使用inner是相當快一點,則純numpy的操作。請注意,Einsum幾乎是純numpy表達式和good reason的兩倍。由於您的循環不是那麼大,大部分FLOPS都在inner計算中,所以對inner操作的保存大於循環成本。

%timeit slowversion(a,norms) 
1 loops, best of 3: 991 ms per loop 

%timeit fastversion(a,norms) 
1 loops, best of 3: 1.28 s per loop 

#Thanks to DSM for writing this out 
%timeit np.einsum('ijk,ij->ik',a.reshape(num_vertices, num_vert_coords, num_pca_dims), norms) 
1 loops, best of 3: 488 ms per loop 

把這個放在一起,我們可以看到「慢版」勝出的整體優勢;然而,使用einsum實現,這是相當優化的這種事情,使我們進一步提高速度。

*我沒有在代碼中看到它,但它顯然是線程化的。

+0

我看不到有任何證據表明'inner1d'是線程化的,或者它使用BLAS。基於[源代碼](https://github.com/numpy/numpy/blob/7b2f20b406d27364c812f7a81a9c901afbd3600c/numpy/core/src/umath/umath_tests.c.src),它看起來像一個非常簡單的C循環。 –

+0

@ali_m我沒有看到任何可以表明這一點的東西,因此是音符。無論使用我筆記本電腦上的'inner1d'功能,清楚地表明它是線程化的,並且BLAS的鏈接將是最明顯的原因。 – Daniel

+0

嗯,這很奇怪 - 在我的手中'inner1d'在numpy 1.7.1和當前的dev版本中似乎都是單線程的。它絕對不使用BLAS(基於'ldd umath_tests.so'的輸出)。無論如何,我當然不懷疑你的答案的主要觀點。 –