對於 Python 的科學計算有哪些提高運算速度的技巧?

最近在用 Python 做科學計算,但是被 Python 的運行速度所困擾。矩陣運算也很慢,想徵求下大家的建議。有什麼優化 Python 科學計算的方法么?或者對於科學計算的語言有什麼推薦么?非常感謝!


說到矩陣運算,最簡單的粗暴的就是三重循環直接遍歷:

def matrix_multiplication_loop(A,B):
m = A.shape[0]
n = A.shape[1]
l = B.shape[1]
C = np.zeros([m,l])
for i in xrange(m):
for j in xrange(l):
for k in xrange(n):
C += A[i][k]*B[k][j]
return C
A = np.random.random([300,12])
B = np.random.random([12,256])
%timeit C = matrix_multiplication_loop(A,B)
1 loop, best of 3: 2.22 s per loop

簡直龜速了,可不可再快一點?當然,上numpy

%timeit C = np.dot(A,B)
10000 loops, best of 3: 105 μs per loop

numpy還是牛牛噠,一下子快了2萬倍~

可不可再快一點?當然,JIT聽過嗎?just in time-即時編譯。我第一次聽到這個詞是在工業工程的精益製造里,它的含義是生產線上即時生產,需要什麼馬上預定什麼,沒有庫存。numba就是just in time的一個編譯器,讓我們來試試:

import numba
@numba.autojit
def matrix_multiplication_numba(A,B):
return np.dot(A,B)
%timeit C = matrix_multiplication_numba(D,E)
10000 loops, best of 3: 55 μs per loop

又快了將近一倍~

可不可再快一點?當然,只是今天沒時間了,未完待續。


numpy本身是非常優秀的,把速度優化就極佳了,要打敗它並不容易,我們需要藉助上古的力量C語言和blas庫。cython是python里實現C語言的一座橋樑,下面是用cython實現的矩陣乘法:

%load_ext Cython
%%cython
#!python
#cython: boundscheck=False, wraparound=False, nonecheck=False
#cython: cdivision=True
from scipy.linalg.cython_blas cimport dgemm

cpdef void cython_blas_MatrixMul(double[::1,:] a, double[::1,:] b, double[::1,:] out, char* TransA, char* TransB) nogil:

cdef:
char* Trans="T"
char* No_Trans="N"
int m, n, k, lda, ldb, ldc
int col_a, col_b
double alpha, beta

#dimensions of input arrays
lda = a.shape[0]
col_a = a.shape[1]
ldb = b.shape[0]
col_b = b.shape[1]
ldc = m

alpha = 1.0
beta = 0.0
dgemm(TransA, TransB, m, n, k, alpha, a[0,0], lda, b[0,0], ldb, beta, out[0,0], ldc)

%timeit cython_blas_MatrixMul(A,B,C,b"T",b"T")
100000 loops, best of 3: 9.34 μs per loop

厲害吧!又快了五倍,比最開始的實現方法已經快了20萬倍!這性能也已經逼近C語言了。

可不可以再快一點?嘿嘿,當然!現在已經接近CPU的極限了,要更快我們就要買入GPU的世界了~

你們感興趣,超過一百贊,我就寫怎麼使用python做GPU計算,讓計算速度快破天際


謝謝大家捧場,這麼快就過100贊了。來來來,讓我們繼續飆車~

GPU相比CPU並非在所有情況下都更快,小矩陣時,矩陣可以直接存儲在CPU的cache里,CPU可以快速訪問,這個時候CPU會比GPU快。但是當遇到大矩陣時,GPU的威力就顯示出來了。讓我們先把矩陣擴大一千倍來看看:

A = np.random.random([3000,1280])
B = np.random.random([1280,2560])
C = np.zeros([3000,2560])

先用numpy做baseline:

%timeit C = np.dot(A,B)
1 loop, best of 3: 582 ms per loop

可怕,一下子慢了5000倍。來試試,cython:

%timeit cython_blas_MatrixMul(A,B,C,b"T",b"T")
1 loop, best of 3: 280 ms per loop

快了一倍,可是還要280ms。讓我們來試試GPU吧。先用pyculib走一波,pyculib是cuda在Python里的一個開源庫,集成了cudablas一系列演算法,非常好用:

from pyculib import blas
%timeit Cres = blas.gemm("N", "N", alpha, A, B)
1 loop, best of 3: 140 ms per loop

哇塞,一下快了一倍,GPU果然厲害~

可不可以再快一點?那是必須的。tensorflow是Google開源的深度學習框架,矩陣方面內部優化很多:

import tensorflow as tf
A = tf.random_normal([3000,1280])
B = tf.random_normal([1280,2560])
C = tf.matmul(A,B)
with tf.Session() as sess:
%timeit result = sess.run(C)
100 loops, best of 3: 4.83 ms per loop

哇咔咔,比numpy快了100倍!tensorflow果然是Google的技術名不虛傳!

這就是終點了嗎?還能更快嗎?答案是肯定的,我聽NVIDIA的工程師說,如果你用C語言編寫的cuDNN直接操作GPU指針還能比tensorflow快3倍~但那就脫離python的範疇了。看了這麼多,有木有覺得計算機真是博大精深!勇敢的少年們,快來擁抱CS吧~


py科學計算用的不太多。也就是最近天天跑數據分析。我簡單說自己的一些基本經驗給比我還菜的新手,拋磚引玉

一:學會正確使用numpy scipy。 numpy scipy寫好的絕不自己寫,比如矩陣運算等操作,pylab的實現還算不錯。各種函數都有,盡量使用他們可以避免初學者大部分的速度不足問題。因為這些函數大部分都是預編譯好的。

根據我幾年前的測試,python的矩陣運算速度並不慢,(因為你運行的是動態鏈接庫裡面的函數而不是腳本)比mathematica快,和matlab持平。

大部分新手不擅長看文檔啥都自己造輪子是不好的。當然老手把效率寫的比開源庫高也不算啥新聞,畢竟有對特定程序的優化

二:減少for的使用,多使用向量化函數,np.vectorlize可以把函數變成對數組逐元素的操作,比for效率高几個華萊士。

三:對內存友好,操作大矩陣的時候減少會引起整矩陣對此copy的操作

四:系統最慢的大部分時候是io,包括上面說的內存操作和頻繁的讀入讀出以及debug輸出。避免他們,在需要實時處理的時候引入類似於gpu的pipeline管線機制或者使用靈活的多線程編程可以起到奇效。

五:matplotlib的繪圖效率並不高明,在使用交互繪圖(plt.ion)的時候減少不必要的刷新率。

六:程序對jit(運行前編譯)友好,避免頻繁的改動數據類型引起的繁雜類型推導(不過很少見科學計算程序有這個毛病)

七:要不然,乾脆把python用cython之類的玩意編譯了吧。。但在科學計算上我覺得這樣做卵用不大。

科學計算最好應該還是使用gpu加速,但關於py如何使用cuda/opencl加速我不太懂。還望後面的朋友補充。一些基本的經驗就這些,應該是入門者都懂了…


numpy 底層全是用 C 寫的,另外還有很多優化,可以說,只要從頭到尾用 numpy 實現,那基本上跟 C 差不多。但是由 python 進入 C 的時候還會損失一些速度。所以最好的方法是從頭到尾用C。 根據 Tensorflow 的說法,它們的圖結構保證了整個計算過程會被完全在 C 上實現,減少了切換過程, 使得程序運行得更快了,所以我推薦用 Tensorflow。另外一個比較好的地方就是, Tensorflow 對 CUDA 的支持非常非常地好,因此你可以不必擔心在自己實現 CUDA 代碼的時候出現諸如 numba 不支持 for 語句,或者是用 pyCUDA 手動擼 C 的不便。早日使用CUDA,早日迎接美好人生。


最實用的方法就是用numpy/pandas,同時多用正則表達式,不要用list/dict/string operation。

而且這樣做可以提高代碼的可讀性。記住代碼是寫給人看的。


你到底處理了多大的數據呀?能不能說具體一點,你用這些數據幹什麼了?怎麼個慢法?


用C++寫核心計算部分


看見回答用c c++的,我覺得他們可能並沒有自己寫過這種部分。

目前最簡單的做法是用Numba,numba是anaconda的公司做的玩意。大概基本上就是一個JIT,會編譯到C。絕大多數時候numba應該就夠了。我之前一個項目從java port到python的時候,核心部分需要提高性能。最後是拿numba寫的。再加上各種優化之後速度是原來java的40倍左右 (不是一種公平的比較就是了)

Numbda性能大概可以做到和numpy相當, 不用numpy是因為並不是所有的性能關鍵部分都是矩陣。

Cython也可以,但是沒有numba簡單。

最後才是c cpp這些。首先最好不要cpp因為性能關鍵的部分其實一般寫成簡單的函數就好了,簡單直觀方便維護。用cpp的代碼一般需要在外麵包一層 (比方用swig,boost.python,貌似新的還有一個pybind 啥的pytorch裡面貌似用了那個)。ctypes來封裝也是可以的。總之我覺得竟可能的把膠水的部分變得簡單一點。然後一般如果你要從cpp c 回調python會複雜一點因為要處理各種GIL,reference count的東西,異常處理。上面的方法回調一般都簡單很多。


用C或者Fortran實現關鍵部分


看了上面所有的回答 不是c就是fortran 我只想問句你處理的項目的是linear 還是nonlinear 科學計算里不能撇開matrices的特點談語言


放棄Python吧。


原來這裡 是知乎


可以試試tensorflow ,當然很有些偏離Python了


python因為所有東西都是對象。。所以box/unbox會耗時間。。然後gil所以同時只能解釋單線程。。

所以想加速。。

1。numpy的array可以理解為整個array是單一box。。所以盡量整體操作會節約時間。

2。把計算intensive的部分 放到其他語言里寫:首先是python/c的api。。不過這樣寫程序就沒有寫python那麼快了。 所以cython也是一個不錯的選擇。。

3。另外針對gil 也是一樣的道理 可以考慮 把計算分給不同的py程序去完成(分數據集,分任務這樣),這樣就可以並行了;另外呢,2的方法也能解決這個問題。

4。有個東西叫numba。。numba.jit算是性價比最高的無腦優化了(不過好像影響精度)。。


瀉藥

學會正確(合理)使用jit


可以用cython,numba,pypy提高Python的運行速度。用GPU做並行的話可以用gnumpy或者tensor flow。Python自身的並行很淡騰,全局解釋鎖使得Python的多線程同時只能利用一個核。想用多核就要用多進程,Python的多進程通信要把對象進行序列化,效率比較低。想大幅度提高運行速度,關鍵部分的代碼還是用Fortran或者C/C++寫比較好。


分散式計算


mpi4py


用multiprocessing。如果沒記錯的話模塊應該叫這個名字,跑程序的時候可以把需要大量計算的東西拆成幾小份。然後分發給每個進程。這樣也算是能變相使用多進程吧。不過這個東西不太好用,應用範圍也挺窄的。別人說的很多看樣子都挺好的。走投無路的時候可以試試這種多進程方法。


轉Julia,轉nim

cython

rust寫擴展。

為什麼不用c或c艹?

你如果能理解gc工作原理,熟悉內存操作愛用用唄.

只不過一般情況下弱智的Python GC都不怎麼管用

恩,Julia最簡潔


推薦閱讀:

基於 Python 的中文分詞方案那種比較好?
Python 多線程效率不高嗎?
Python 開發中有哪些高級技巧?
excel中想實現使用Python代替VBA,請問應該怎麼做?

TAG:Python | 物理學 | 科學計算 | 計算物理學 |