[英]How to improve efficiency on parallel loops in Python
我很好奇 Python 中的並行循環與 Matlab 中的parloop
相比效率有多低。 在這里,我提出了一個簡單的尋根問題,在a
和b
之間強制進行初始 10^6 初始猜測。
import numpy as np
from scipy.optimize import root
import matplotlib.pyplot as plt
import multiprocessing
# define the function to find the roots
func = lambda x: np.sin(3*np.pi*np.cos(np.pi*x)*np.sin(np.pi*x))
def forfunc(x0):
q = [root(func, xi).x for xi in x0]
q = np.array(q).T[0]
return q
# variables os the problem
a = -3
b = 5
n = int(1e6)
x0 = np.linspace(a,b,n) # list of initial guesses
# the single-process loop
q = forfunc(x0)
# parallel loop
nc = 4
pool = multiprocessing.Pool(processes=nc)
q = np.hstack(pool.map(forfunc,np.split(x0,nc)))
pool.close()
單進程循環需要 1 分 26 秒的時間,並行循環需要 1 分 7 秒。 我看到一些改進,因為加速為 1.28,但在這種情況下效率(timeloop/timeparallel/n_process)
為 0.32。
這里發生了什么以及如何提高這種效率? 難道我做錯了什么?
我還嘗試通過兩種方式使用dask.delayed
:
import dask
# Every call is a delayed object
q = dask.compute(*[dask.delayed(func)(xi) for xi in x0])
# Every chunk is a delayed object
q = dask.compute(*[dask.delayed(forfunc)(x0i) for x0i in np.split(x0,nc)])
並且這里兩者都比單進程循環花費更多的時間。 第一次嘗試的牆上時間是 3 分鍾,第二次嘗試用了 1 分 27 秒。
從您的單進程測試來看,您的循環在 90 秒內執行了 100 萬個任務。 因此,在平均情況下,每個任務占用 CPU 大約 90 微秒。
在 Dask 或 Spark 等提供靈活性和彈性的分布式計算框架中,任務的相關開銷很小。 Dask 的開銷低至每個任務200 微秒。 Spark 3.0 文檔表明 Spark 可以支持短至 200毫秒的任務,這可能意味着 Dask 的開銷實際上比 Spark 少 1000 倍。 聽起來 Dask 實際上在這里做得非常好!
如果您的任務比框架的每個任務開銷更快,則相對於在相同數量的機器/內核上手動分配您的工作,您只會看到使用它的性能更差。 在這種情況下,您會遇到這種情況。
在分塊數據 Dask 示例中,您只有幾個任務,因此您可以通過減少開銷看到更好的性能。 但是,相對於原始多處理,您可能會從 Dask 的開銷中獲得較小的性能損失,或者您沒有使用 Dask 集群並在單個進程中運行任務。
對於這種令人尷尬的並行問題,您的多處理結果通常是出乎意料的。 您可能需要確認機器上的物理內核數量,特別是確保沒有其他東西正在積極使用您的 CPU 內核。 在不知道其他任何事情的情況下,我猜這就是罪魁禍首。
在我有兩個物理內核的筆記本電腦上,您的示例采用:
nc=2
的分塊 Dask 示例,需要 1 分鍾 5 秒才能拆分為兩個塊和一個由兩個工作人員組成的 LocalCluster,每個工作人員有一個線程。 可能值得仔細檢查您是否在集群上運行。通過兩個進程獲得大約 2 倍的加速符合我的筆記本電腦的預期,因為對於這個 CPU 密集型任務,從更多進程中看到的好處很少或沒有好處。 Dask 還增加了一些相對於原始多處理的開銷。
%%time
# the single-process loop
q = forfunc(x0)
CPU times: user 1min 55s, sys: 1.68 s, total: 1min 57s
Wall time: 2min 1s
%%time
# parallel loop
nc = 2
pool = multiprocessing.Pool(processes=nc)
q = np.hstack(pool.map(forfunc,np.split(x0,nc)))
pool.close()
CPU times: user 92.6 ms, sys: 70.8 ms, total: 163 ms
Wall time: 1min 2s
%%time
# parallel loop
nc = 4
pool = multiprocessing.Pool(processes=nc)
q = np.hstack(pool.map(forfunc,np.split(x0,nc)))
pool.close()
CPU times: user 118 ms, sys: 94.6 ms, total: 212 ms
Wall time: 1min
from dask.distributed import Client, LocalCluster, wait
client = Client(n_workers=2, threads_per_worker=1)
%%time
nc = 2
chunks = np.split(x0,nc)
client.scatter(chunks, broadcast=True)
q = client.compute([dask.delayed(forfunc)(x0i) for x0i in chunks])
wait(q)
/Users/nickbecker/miniconda3/envs/prophet/lib/python3.7/site-packages/distributed/worker.py:3382: UserWarning: Large object of size 4.00 MB detected in task graph:
(array([1.000004, 1.000012, 1.00002 , ..., 4.99998 ... 2, 5. ]),)
Consider scattering large objects ahead of time
with client.scatter to reduce scheduler burden and
keep data on workers
future = client.submit(func, big_data) # bad
big_future = client.scatter(big_data) # good
future = client.submit(func, big_future) # good
% (format_bytes(len(b)), s)
CPU times: user 3.67 s, sys: 324 ms, total: 4 s
Wall time: 1min 5s
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.