繁体   English   中英

python 多进程使用 map,但有一个子进程正在运行

[英]python multiprocess using map, but with one sub-process running

我是 python map() function 来实现并行代码的新手。

def main_function(sample):
    # ......(only input file; calculations; and output file)

if __name__ == "__main__":
    list_sample_common = os.listdir('/lustre/scratch/Stat/s1155136154/ONT_Panel2')# WES,ONT_panel, Pacibo_Panel intersection.
    list_sample_Pacibo_normal = ['RMH12', 'RMH15','RMH20','RMH25','RMH3.','RMH7.','RMH9.']# normal people sample
    list_sample_ONT_cDNA_only = ['RM66T','RM68T','RM77T']
    sample = list_sample_common + list_sample_Pacibo_normal + list_sample_ONT_cDNA_only
    pool=Pool()
    pool.map(main_function,sample)
    pool.close()
    pool.join()

所以当我第一次在集群上使用它时,子进程以 500% 的 CPU 运行(因为我在集群中应用了 5 个核心)。

然而,一段时间后,只有一个核心在运行:
最佳结果

那么,其原因是主要的 function 包含 output 和输入操作? 由于主进程只将短名单传递给子 function,我确信参数大小不会影响速度。

这只是一个有根据的猜测,因为我对sample的大小以及您的工人 function, main_function正在执行的工作的详细信息知之甚少

让我们假设您传递给Pool.map方法的可迭代sample的长度为 70,正如您所说,您的池大小为 5。 map方法会将 70 个任务分解为chunksize大小的任务组,分配这些块到池中的 5 个进程中的每一个。 如果您没有为map方法指定chunksize参数,它会根据迭代的大小 (70) 和池的大小 (5) 计算值,如下所示:

def compute_chunksize(iterable_size, pool_size):
    chunksize, remainder = divmod(iterable_size, pool_size * 4)
    if remainder:
        chunksize += 1
    return chunksize

因此,对于您的值, chunksize将为 4。因此将有 17 个大小为 4 的任务块和一个较小的第 18 个大小为 2 的块分布在 5 个进程中(每列是池中给定进程的任务队列) :

4 4 4 4 4
4 4 4 4 4
4 4 4 4 4
4 4 2

假设所有任务的处理时间相同,您可以看到,经过一定时间后,最后 2 个进程将完成分配给它们的 12 个任务,现在将处于空闲状态,您将仅以 60% 的速度运行。 最终,第三个进程将完成其任务,您现在将以 40% 的速度运行。

但是您可以看到sample大小和池大小的正确组合,您可能会遇到只运行一个进程的情况。 较大的块大小值会加剧这种情况,这旨在减少排队任务所需的共享chunksize访问的数量,但可能会导致 CPU 利用率低下。

作为一个实验,尝试重新运行您的程序,为您的chunksize调用显式指定map参数 1。 除非任务数量是池大小的倍数,并且每个任务都需要相同的时间来完成,否则即使这样,您也不能期望每个处理器都有一个任务要运行。 事实上,很少有这样的情况,您只有一个进程正在运行最终任务。 但这应该会减少只有一个处理器忙碌的时间百分比。 但是对于大型迭代,使用 1 的块大小被认为是chunksize的。

带有 4 个进程池的演示,其中第一个进程获取所有长时间运行的任务

这里有 16 个任务以 4 的块大小提交到 4 的池大小,以便第一个进程运行前 4 个任务,并且人为地使这些任务的运行时间比chunksize长 10 倍。 我们返回与子流程关联的标识符,以证明一个特定流程正在处理前 4 个任务:

from multiprocessing import Pool, current_process
import re
import time

def get_id():
    m = re.search(r'SpawnPoolWorker-(\d+)', str(current_process()))
    return int(m[1])

def worker(i):
    R = 10000000
    id = get_id()
    t = time.time()
    # run up the cpu:
    cnt = 0
    for _ in range(R * 10 if i <= 3 else R):
        cnt += 1
    return i, id, time.time() - t



if __name__ == '__main__':
    p = Pool(4)
    # 4 tasks per process:
    results = p.map(worker, range(16), chunksize=4) # first process gets arguments: 0, 1, 2, 3
    for result in results:
        i, id, elapsed_time = result
        print(f'i={i}, process id={id}, elapsed time={elapsed_time}')

印刷:

i=0, process id=1, elapsed time=6.197998046875
i=1, process id=1, elapsed time=5.889002323150635
i=2, process id=1, elapsed time=5.952000856399536
i=3, process id=1, elapsed time=6.022995948791504
i=4, process id=2, elapsed time=0.6909992694854736
i=5, process id=2, elapsed time=0.8339993953704834
i=6, process id=2, elapsed time=0.5869994163513184
i=7, process id=2, elapsed time=0.7560005187988281
i=8, process id=3, elapsed time=0.7500002384185791
i=9, process id=3, elapsed time=0.7440023422241211
i=10, process id=3, elapsed time=0.7600002288818359
i=11, process id=3, elapsed time=0.7479968070983887
i=12, process id=4, elapsed time=0.7950015068054199
i=13, process id=4, elapsed time=0.7909986972808838
i=14, process id=4, elapsed time=0.8639986515045166
i=15, process id=4, elapsed time=0.7230024337768555

重要提示:我可能说过某些事情是对实际发生的事情的简化。 有一个任务输入队列。 任务以块chunksize小组的块的形式放置在此队列中,并且池中的进程在空闲时将下一个chunksize大小组从队列中取出以进行处理。 我在图表中暗示,这些块在开始时已预先分配给所有进程,但事实并非如此。 在我上面的演示中,我选择了一个chunksize导致所有块都被处理的块大小(如果没有指定,默认的块chunksize将是 1)。 但有时如果任务的处理是微不足道的(例如,只是一个return None语句),第一个进程甚至可能获取所有块,而在上面的演示中不是这种情况。 具有所有块的单个队列的含义是,当chunksize大小为 1 时,处理器永远不应不必要地空闲。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM