[英]Python for loop: Proper implementation of multiprocessing
以下 for 循環是迭代模擬過程的一部分,是計算時間的主要瓶頸:
import numpy as np
class Simulation(object):
def __init__(self,n_int):
self.n_int = n_int
def loop(self):
for itr in range(self.n_int):
#some preceeding code which updates rows_list and diff with every itr
cols_red_list = []
rows_list = list(range(2500)) #row idx for diff where negative element is known to appear
diff = np.random.uniform(-1.323, 3.780, (2500, 300)) #np.random.uniform is just used as toy example
for row in rows_list:
col = next(idx for idx, val in enumerate(diff[row,:]) if val < 0)
cols_red_list.append(col)
# some subsequent code which uses the cols_red_list data
sim1 = Simulation(n_int=10)
sim1.loop()
因此,我嘗試通過使用multiprocessing包來並行化它,以期減少計算時間:
import numpy as np
from multiprocessing import Pool, cpu_count
from functools import partial
def crossings(row, diff):
return next(idx for idx, val in enumerate(diff[row,:]) if val < 0)
class Simulation(object):
def __init__(self,n_int):
self.n_int = n_int
def loop(self):
for itr in range(self.n_int):
#some preceeding code which updates rows_list and diff with every
rows_list = list(range(2500))
diff = np.random.uniform(-1, 1, (2500, 300))
if __name__ == '__main__':
num_of_workers = cpu_count()
print('number of CPUs : ', num_of_workers)
pool = Pool(num_of_workers)
cols_red_list = pool.map(partial(crossings,diff = diff), rows_list)
pool.close()
print(len(cols_red_list))
# some subsequent code which uses the cols_red_list data
sim1 = Simulation(n_int=10)
sim1.loop()
不幸的是,與順序代碼相比,並行化要慢得多。 因此我的問題是:在那個特定的例子中我是否正確使用了 multiprocessing 包? 是否有其他方法可以並行化上述 for 循環?
免責聲明:當您試圖通過並行化減少代碼的運行時間時,這並不能嚴格回答您的問題,但它可能仍然是一個很好的學習機會。
作為黃金法則,在轉向多處理以提高性能(執行時間)之前,應該首先優化單線程情況。
您的
rows_list = list(range(2500))
生成數字0
到2499
(即range
)並將它們存儲在內存中( list
),這需要時間來分配所需的內存和實際寫入。 然后,您只使用這些可預測值一次,方法是從內存中讀取它們(這也需要時間),以可預測的順序:
for row in rows_list:
當您重復執行時,這與loop
函數的運行時特別相關( for itr in range(n_int):
)。
相反,請考慮僅在需要時生成數字,而無需中間存儲(這在概念上消除了訪問 RAM 的任何需要):
for row in range(2500):
其次,除了共享相同的問題(對內存的不必要訪問)之外,還有以下內容:
diff = np.random.uniform(-1, 1, (2500, 300))
# ...
col = next(idx for idx, val in enumerate(diff[row,:]) if val < 0)
在我看來,在數學(或邏輯)層面上是可以優化的。
您要做的是通過將其定義為“我第一次在 [-1;1] 中遇到低於 0 的隨機變量”來獲得一個隨機變量(即col
索引)。 但是請注意,確定在 [-α;α] 上具有均勻分布的隨機變量是否為負,與在 {0,1} 上(即bool
)具有隨機變量相同。
因此,您現在使用的是bool
s 而不是float
s,您甚至不必進行比較( val < 0
),因為您已經有了 bool 。 這可能會使代碼更快。 使用與rows_list
相同的想法,您可以僅在需要時生成該bool
; 測試它直到它為True
(或False
,選擇一個,這顯然無關緊要)。 通過這樣做,你只生成你需要的隨機bool
s,不多也不少(順便說一句,如果行中的所有 300 個元素都是負數,你的代碼會發生什么?;)):
for _ in range(n_int):
cols_red_list = []
for row in range(2500):
col = next(i for i in itertools.count() if random.getrandbits(1))
cols_red_list.append(col)
或者,使用列表理解:
cols_red_list = [next(i for i in count() if getrandbits(1))
for _ in range(2500)]
我敢肯定,通過適當的統計分析,您甚至可以將col
隨機變量表示為 [0; 上的非均勻變量; limit
[,讓您可以更快地計算它。
請先測試您的單線程實現的“優化”版本的性能。 如果運行時仍然不可接受,那么您應該研究多線程。
multiprocessing
使用系統進程(而不是線程!)進行並行化,這需要昂貴的 IPC(進程間通信)來共享數據。
這在兩個方面咬你:
diff = np.random.uniform(-1, 1, (2500, 300))
創建一個大矩陣,這對於pickle/復制到另一個進程來說很昂貴rows_list = list(range(2500))
創建一個較小的列表,但這里同樣適用。為了避免這種昂貴的 IPC,您有一個半選擇:
mp.Array
應該可以滿足您的需求。 第二個問題是設置池很昂貴,因為需要啟動num_cpu
進程。 與此開銷相比,您的工作負載小到可以忽略不計。 一種好的做法是只創建一個池並重用它。
這是僅 POSIX 解決方案的一個簡單粗暴的示例:
import numpy as np
from multiprocessing import Pool, cpu_count
from functools import partial
n_int = 10
rows_list = np.array(range(2500))
diff = np.random.uniform(-1, 1, (2500, 300))
def crossings(row, diff):
return next(idx for idx, val in enumerate(diff[row,:]) if val < 0)
def workload(_):
cols_red_list = [crossings(row, diff) for row in rows_list]
print(len(cols_red_list))
class Simulation(object):
def loop(self):
num_of_workers = cpu_count()
with Pool(num_of_workers) as pool:
pool.map(workload, range(10))
pool.close()
sim1 = Simulation()
sim1.loop()
對我(和我的兩個核心)來說,這大約是順序版本的兩倍。
使用共享內存更新:
import numpy as np
from multiprocessing import Pool, cpu_count, Array
from functools import partial
n_int = 10
ROW_COUNT = 2500
### WORKER
diff = None
result = None
def init_worker(*args):
global diff, result
(diff, result) = args
def crossings(i):
result[i] = next(idx for idx, val in enumerate(diff[i*300:(i+1)*300]) if val < 0)
### MAIN
class Simulation():
def loop(self):
num_of_workers = cpu_count()
diff = Array('d', range(ROW_COUNT*300), lock=False)
result = Array('i', ROW_COUNT, lock=False)
# Shared memory needs to be passed when workers are spawned
pool = Pool(num_of_workers, initializer=init_worker, initargs=(diff, result))
for i in range(n_int):
# SLOW, I assume you use a different source of values anyway.
diff[:] = np.random.uniform(-1, 1, ROW_COUNT*300)
pool.map(partial(crossings), range(ROW_COUNT))
print(len(result))
pool.close()
sim1 = Simulation()
sim1.loop()
一些注意事項:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.