我有一个非常耗费处理器的任务,需要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?
谢谢!
答案 0 :(得分:0)
这是一个如何使用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
似乎将自己限制为一个核心,但如果没有,则应考虑将每个工作线程固定在一个特定的内核上,以避免工作进程之间发生争用(您可以这样做在Linux上执行psutil.Process().cpu_affinity([i])
,其中i
是要绑定的核心的索引。ray timeline
并在chrome:// tracing(在Chrome网络浏览器中)中可视化结果来进行检查。K
矩阵进行序列化和反序列化。这是重要的性能优化(尽管任务是否花费很长时间并不重要)。这主要对包含大型numpy数组的对象有帮助。它对任意Python对象没有帮助。这可以通过使用Apache Arrow数据布局来启用。您可以在this blog post中阅读更多内容。您可以在Ray documentation中看到更多内容。请注意,我是Ray开发人员之一。