[英]Python Multiprocessing: Only one process is running
我正在嘗試使用Python多處理模塊生成多個並行進程。 基本上,我做了類似的事情
pool = Pool(30)
results = [pool.apply_async(foo, (trainData, featureVector, terms, selLabel)) for selLabel in selLabels]
for r in results:
tmp = r.get()
modelFiles[tmp[0]] = tmp[1]
產生了30個進程,但是,似乎大多數進程已進入休眠狀態,而實際只有一個進程正在運行。 以下是我從ps得到的:
PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
31267 74.6 2.4 7125412 6360080 pts/1 Sl+ 13:06 24:25 \_ python2.6 /home/PerlModules/Python/DoOVA.py
31427 27.4 2.3 6528532 6120904 pts/1 R+ 13:20 5:18 \_ python2.6 /home/PerlModules/Python/DoOVA.py
31428 0.0 1.3 4024724 3617016 pts/1 S+ 13:20 0:00 \_ python2.6 /home/PerlModules/Python/DoOVA.py
31429 0.0 1.3 4024724 3617016 pts/1 S+ 13:20 0:00 \_ python2.6 /home/PerlModules/Python/DoOVA.py
31430 0.0 1.3 4024724 3617016 pts/1 S+ 13:20 0:00 \_ python2.6 /home/PerlModules/Python/DoOVA.py
DoOVA.py
是我正在運行的腳本。 他們中的大多數人的身份都是S+
。
誰能給我一些關於問題的線索? 我知道輸入爭論featureVector
大小非常大,比如大約300MB。 那會是個問題嗎? 我運行的機器有幾TB的內存。
foo做的事情如下:
def foo(trainData, featureVector, terms, selLabel, penalty):
outputFile = 'train_'+selLabel+'.dat'
annotation = dict()
for id in trainData:
if trainData[id] == selLabel:
annotation[id] = '1'
else:
annotation[id] = '-1'
try:
os.mkdir(selLabel)
os.chdir(selLabel)
except OSError:
os.chdir(selLabel)
###Some more functions, which involves a command line call through call from subprocess module
os.chdir('../')
return (selLabel, 'SVM_' + selLabel + '.model')
所有其他輸入參數的大小都很小。 機器至少有100 cpus。 在每次運行中,即使在創建任何目錄之前,腳本也需要很長時間,盡管在os.mkdir()之前foo中沒有發生重大計算。
正如評論指出要通過featureVector
使用initializer
和initargs
參數Pool
。 在Unix類型的系統上,這將導致大量的性能提升(即使selLabels
只有1個項目),因為該值將使用os.fork
基本上免費傳遞給子進程。 否則,每次調用foo
, featureVector
都將被父進程pickle,通過管道傳遞並由子進程進行unpickled。 這將花費很長時間,並且基本上將序列化所有子進程,因為它們將等待父進程腌制並featureVector
發送每個調用的featureVector
副本。
由於對於我上面談論的內容存在一些困惑,所以這里有一個更長的解釋,說明代碼中發生的內容與當前編寫的內容有關:
創建Pool
對象時,將立即創建30個工作進程,主進程的所有子進程都創建了Pool
對象。 為了與每個子進程進行通信,創建了一個管道。 此管道允許父進程和子進程之間的雙向通信。 父級使用管道來指示子進程執行的操作,子級使用管道通知父級任何操作的結果。
當您第一次調用pool.apply_async
,父進程通過管道發送命令,指示子進程使用提供的參數執行foo
函數。 由於其中一個論點是巨大的,300MB,這最終需要很長時間。 父進程必須pickle對象。 這會將對象(及其引用的所有內容)轉換為可通過管道發送的字節流。
由於管道只能容納大約64k(Linux默認值),並且您發送的內容遠不止這些,因此可以有效地同步父進程和其中一個子進程。 父進程只能以子進程可以接收和取消它們的速度發送參數,並且子進程只能像父進程一樣快地接收參數並發送它們。 雖然這是在進行所有其他子進程必須等待。 父進程一次只能向一個子進程發送命令。
一旦父進程完成了第一次調用foo
所有參數的發送,它就可以繼續發送命令再次調用foo
。 在此之后不久,一旦子進程收到所有參數,孩子就會調用foo
。 (這就是為什么在創建任何目錄之前需要很長時間,甚至在foo
被調用之前需要很長時間。)在foo
返回之后,子進程將等待父進程發送另一個命令。 如果foo
本身需要足夠短的時間來執行,那么接收第一個命令來調用foo
同一子進程也可能會收到第二個調用foo
命令。
除非foo
本身需要很長時間才能執行,只要比通過管道發送featureVector
花費的時間長或長,那么你將被有效地限制為只執行一個子進程。 父進程將嘗試命令子進程盡可能快地調用foo
,但由於featureVector
太大,它只能以非常慢的速率執行。 一旦完成將命令發送到一個進程來調用foo
,它命令調用foo
的前一個進程很久以前就已經完成了對foo
調用。 運行子進程之間幾乎沒有重疊。
為了解決代碼中的性能問題,您需要執行以下操作:
def child_initialize(_trainData, _featureVector, _terms):
global trainData, featureVector, terms
trainData = _trainData
featureVector = _featureVector
terms = _terms
def foo(selLabel):
...
pool = Pool(30, initialize = child_initialize, initargs = (trainData, featureVector, terms))
results = [pool.apply_async(foo, (selLabel,)) for selLabel in selLabels]
此代碼還使用initargs
傳遞trainData
和term
,假設它們也不會更改。
雖然這應該會帶來巨大的性能提升,並允許子進程並行運行,但這並不一定意味着子進程將以更常見的狀態出現在可運行狀態的ps
中。 您的示例foo
函數看起來似乎將花費大部分時間等待“命令行調用”完成。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.