[英]Minimize overhead in Python multiprocessing.Pool with numpy/scipy
[英]Python multiprocessing slow with numpy/scipy
我有一個非常耗費處理器的任務,需要13-20個小時才能完成,具體取決於機器。 似乎是通過多處理庫進行並行化的明顯選擇。 問題是...我產生的進程越多,相同代碼的速度就越慢。
每次迭代的時間(即運行sparse.linalg.cg所需的時間):
183s 1程序
245s 2個過程
312s 3個過程
383s 4進程
當然,雖然2個進程每次迭代花費的時間略多於30%,但它同時執行2個進程,因此速度仍要快一些。 但是我不希望實際的數學運算本身會變慢! 這些計時器要等到任何開銷的多處理之后才開始。
這是我的代碼的精簡版。 問題行是sparse.linalg.cg之一。 (我嘗試過使用MKL和OpenBLAS之類的方法,並強迫它們在單個線程中運行。還嘗試了手動生成進程而不是使用池。沒有運氣。)
def do_the_thing_partial(iteration: int, iter_size: float, outQ : multiprocessing.Queue, L: int, D: int, qP: int, elec_ind: np.ndarray, Ic: int, ubi2: int,
K : csc_matrix, t: np.ndarray, dip_ind_t: np.ndarray, conds: np.ndarray, hx: float, dstr: np.ndarray):
range_start = ceil(iteration * iter_size)
range_end = ceil((iteration + 1) * iter_size)
for rr in range(range_start, range_end):
# do some things (like generate F from rr)
Vfull=sparse.linalg.cg(K,F,tol=1e-11,maxiter=1200)[0] #Solve the system
# do more things
outQ.put((rr, Vfull))
def do_the_thing(L: int, D: int, qP: int, elec_ind: np.ndarray, Ic: int, ubi2: int,
K : csc_matrix, t: np.ndarray, dip_ind_t: np.ndarray, conds: np.ndarray, hx: float, dstr: np.ndarray):
num_cores = cpu_count()
iterations_per_process = (L-1) / num_cores # 257 / 8 ?
outQ = multiprocessing.Queue()
pool = multiprocessing.Pool(processes=num_cores)
[pool.apply_async(do_the_thing_partial,
args=(i, iterations_per_process, outQ, L, D, qP, elec_ind, Ic, ubi2, K, t, dip_ind_t, conds, hx, dstr),
callback=None)
for i in range(num_cores)]
pool.close()
pool.join()
for res in outQ:
# combine results and return here
我是在做錯什么,還是由於其自身的優化而無法並行化sparse.linalg.cg?
謝謝!
這是一個如何使用Ray (並行和分布式Python庫)加速的示例。 在執行pip install ray
(在Linux或MacOS上),您可以運行以下代碼。
在筆記本電腦上運行下面的串行計算版本(例如,執行scipy.sparse.linalg.cg(K, F, tol=1e-11, maxiter=100)
20次)需要33秒 。 定時下面的代碼來啟動20個任務並獲得結果需要8.7秒 。 我的筆記本電腦有4個物理核心,因此幾乎是4倍的加速 。
我對您的代碼進行了很多更改,但我認為我保留了其本質。
import numpy as np
import ray
import scipy.sparse
import scipy.sparse.linalg
# Consider passing in 'num_cpus=psutil.cpu_count(logical=True)'.
ray.init()
num_elements = 10**7
dim = 10**4
data = np.random.normal(size=num_elements)
row_indices = np.random.randint(0, dim, size=num_elements)
col_indices = np.random.randint(0, dim, size=num_elements)
K = scipy.sparse.csc_matrix((data, (row_indices, col_indices)))
@ray.remote
def solve_system(K, F):
# Solve the system.
return scipy.sparse.linalg.cg(K, F, tol=1e-11, maxiter=100)[0]
# Store the array in shared memory first. This is optional. That is, you could
# directly pass in K, however, this should speed it up because this way it only
# needs to serialize K once. On the other hand, if you use a different value of
# "K" for each call to "solve_system", then this doesn't help.
K_id = ray.put(K)
# Time the code below!
result_ids = []
for _ in range(20):
F = np.random.normal(size=dim)
result_ids.append(solve_system.remote(K_id, F))
# Run a bunch of tasks in parallel. Ray will schedule one per core.
results = ray.get(result_ids)
調用ray.init()
啟動Ray工作進程。 對solve_system.remote
的調用將任務提交給工作人員。 盡管您可以通過@ray.remote(num_cpus=2)
指定一個特定的任務需要更多的資源(或更少的資源),但是Ray會默認為每個內核調度一個。 您還可以指定GPU資源和其他自定義資源。
對solve_system.remote
的調用會立即返回一個代表最終計算結果的ID,而對ray.get
的調用將獲取這些ID並檢索實際的計算結果(因此ray.get
將等待任務完成執行)。
一些注意事項
scipy.sparse.linalg.cg
似乎將自己限制為一個核心,但是如果沒有,則應考慮將每個工作線程固定在一個特定的內核上,以避免工作進程之間發生爭用(您可以在通過執行psutil.Process().cpu_affinity([i])
在Linux中,其中i
是要綁定的核心的索引。 ray timeline
並在chrome:// tracing中可視化結果(在Chrome網絡瀏覽器中)來進行檢查。 K
矩陣進行序列化和反序列化。 這是重要的性能優化(盡管任務是否花費很長時間並不重要)。 這主要對包含大型numpy數組的對象有幫助。 它對任意Python對象沒有幫助。 這是通過使用Apache Arrow數據布局啟用的。 您可以在此博客文章中閱讀更多內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.