我想用許多不同的參數組合來集成一個微分方程組,並存儲變量的最終值屬於一組參數。因此,我實現了一個簡單的for循環,其中創建了隨機初始條件和參數組合,整合了系統並將感興趣的值存儲在各個陣列中。 因爲我打算爲一個相當複雜的系統(這裏我只使用一個玩具系統進行說明)的許多參數組合來做到這一點,而這個系統也可能變得僵硬,我想通過並行化模擬來加速使用Python的「多處理「模塊。Python的多處理:爲幾組參數加速for循環,「apply」與「apply_async」
但是,當我運行模擬時,for循環總是比其並行版本更快。到目前爲止我發現的比for循環更快的唯一方法是使用「apply_async」而不是「apply」。對於10個不同的參數組合,我得到例如以下輸出(使用代碼從下面):
The for loop took 0.11986207962 seconds!
[ 41.75971761 48.06034375 38.74134139 25.6022232 46.48436046
46.34952734 50.9073202 48.26035086 50.05026187 41.79483135]
Using apply took 0.180637836456 seconds!
41.7597176061
48.0603437545
38.7413413879
25.6022231983
46.4843604574
46.3495273394
50.9073202011
48.2603508573
50.0502618731
41.7948313502
Using apply_async took 0.000414133071899 seconds!
41.7597176061
48.0603437545
38.7413413879
25.6022231983
46.4843604574
46.3495273394
50.9073202011
48.2603508573
50.0502618731
41.7948313502
雖然在該示例中,結果的順序是「應用」相同,並且「apply_async」,這似乎一般來說不是真實的。所以,我想使用「apply_async」,因爲它更快,但在這種情況下,我不知道如何將模擬的結果與我用於各自模擬的參數/初始條件相匹配。因此
我的問題是:
1)爲什麼是「應用」比簡單的for循環在這種情況下,多慢些?
2)當我使用「apply_async」而不是「apply」時,並行化版本變得比for循環快得多,但是我怎樣才能將仿真結果與我在各自仿真中使用的參數相匹配?
3)在這種情況下,「apply」和「apply_async」的結果具有相同的順序。這是爲什麼?巧合?
我的代碼可以發現如下:
from pylab import *
import multiprocessing as mp
from scipy.integrate import odeint
import time
#my system of differential equations
def myODE (yn,tvec,allpara):
(x, y, z) = yn
a, b = allpara['para']
dx = -x + a*y + x*x*y
dy = b - a*y - x*x*y
dz = x*y
return (dx, dy, dz)
#for reproducibility
seed(0)
#time settings for integration
dt = 0.01
tmax = 50
tval = arange(0,tmax,dt)
numVar = 3 #number of variables (x, y, z)
numPar = 2 #number of parameters (a, b)
numComb = 10 #number of parameter combinations
INIT = zeros((numComb,numVar)) #initial conditions will be stored here
PARA = zeros((numComb,numPar)) #parameter combinations for a and b will be stored here
RES = zeros(numComb) #z(tmax) will be stored here
tic = time.time()
for combi in range(numComb):
INIT[combi,:] = append(10*rand(2),0) #initial conditions for x and y are randomly chosen, z is 0
PARA[combi,:] = 10*rand(2) #parameter a and b are chosen randomly
allpara = {'para': PARA[combi,:]}
results = transpose(odeint(myODE, INIT[combi,:], tval, args=(allpara,))) #integrate system
RES[combi] = results[numVar - 1][-1] #store z
#INIT[combi,:] = results[:,-1] #update initial conditions
#INIT[combi,-1] = 0 #set z to 0
toc = time.time()
print 'The for loop took ', toc-tic, 'seconds!'
print RES
#function for the multi-processing part
def runMyODE(yn,tvec,allpara):
return transpose(odeint(myODE, yn, tvec, args=(allpara,)))
tic = time.time()
pool = mp.Pool(processes=4)
results = [pool.apply(runMyODE, args=(INIT[combi,:],tval,{'para': PARA[combi,:]})) for combi in range(numComb)]
toc = time.time()
print 'Using apply took ', toc-tic, 'seconds!'
for sol in range(numComb):
print results[sol][2,-1] #print final value of z
tic = time.time()
resultsAsync = [pool.apply_async(runMyODE, args=(INIT[combi,:],tval,{'para': PARA[combi,:]})) for combi in range(numComb)]
toc = time.time()
print 'Using apply_async took ', toc-tic, 'seconds!'
for sol in range(numComb):
print resultsAsync[sol].get()[2,-1] #print final value of z
正如指出的當前的答案,您的異步應用程序是假的,因爲您在打印出時間之前沒有讓工作完成。異步性的要點是避免在工作完成時阻塞調用線程。也就是說,並行循環處理獲得實際性能收益的第一條原則是確保每個線程/任務都有足夠的工作要做。與單線程for循環相比,執行這些任務的開銷調度線程更多,因此您必須通過在每次迭代中執行更多的操作來彌補開銷。 –
例如,如果要並行化類似計算頂點法線的內容,如果循環的每次迭代只計算一個頂點的法線,則會損害性能。你希望每次迭代計算數千個正常值來彌補該線程調度開銷。以這種方式編寫代碼,以便每個線程都有非常豐富的工作要做,並且您將開始看到加速功能開始與您的硬件功能越來越成比例。 –
感謝您的意見,艾克!我的 - 顯然 - 天真的想法是,如果我有一定數量的參數組合,並且在for循環中,它需要一個T來集成它們,如果我使用並行方法,則最終的時間爲〜T/4有4個核心。你是對的,在這個特殊的例子中,計算任務不是那麼「有趣」。然而,存在隨機參數導致系統僵硬的系統,這導致了較長的積分時間。在等待這樣一個僵硬的系統集成的同時,我想同時集成幾個非僵硬的系統。 – Cleb