多进程处理快速计算

2 投票
1 回答
574 浏览
提问于 2025-05-01 16:39

我遇到了一个有趣的多进程问题,可能可以利用它的结构。这个问题涉及一个大约有80列的DataFramedf),里面有很多列,还有一个函数func,这个函数会对列的组合(大约有80*79/2对)进行操作,每次运行的时间都比较短。

代码看起来是这样的:

mgr = Manager()
ns = mgr.Namespace()
ns.df = df

pool = Pool(processes=16)
args = [(ns, list(combo)) for combo in list(combinations(df.columns, 2))]
results = pool.map(func, args)
pool.close()

上面的代码运行速度并不快,但比没有使用池要快,大约快了7倍左右。我担心这么多调用的开销可能是问题所在。有没有好的方法可以利用这里的结构来进行多进程处理呢?

暂无标签

1 个回答

0

这个结果其实很常见。在并行运行时,没有什么能完美地线性扩展,因为每个进程的设置和进程间数据传递都需要一些额外的开销。要记住,(80 * 79) / 2 = 3,160 这个数字其实很小,前提是这个函数不是特别耗费计算资源(也就是说,执行时间不会特别长)。在其他条件相同的情况下,函数越快,使用多进程的额外开销就越大,因为设置一个新进程所需的时间是相对固定的。

多进程的开销主要来自内存,如果你需要对一个大数据集进行多次复制(每个进程都要复制一次,如果函数设计得不好),因为进程之间不共享内存。假设你的函数设置得可以轻松并行化,增加更多的进程是好的,只要不超过你电脑的处理器数量。大多数家用电脑没有16个处理器(通常最多8个),而你得到的结果(并行运行快7倍)也符合你处理器少于16个的情况。你可以通过 multiprocessing.cpu_count() 来查看你机器上的处理器数量。

编辑:

如果你通过传递列字符串来并行化一个函数,那么它会重复复制数据框。例如:

def StringPass(string1, string2):
    return df[string1] * df[string2]

如果你并行化 StringPass,它会至少为每个进程复制一次数据框。相对而言:

def ColumnPass(column1, column2):
    return column1 * column2

如果你只传递必要的列,ColumnPass 在并行运行时只会复制每次调用函数所需的列。因此,虽然 StringPass(string1, string2)ColumnPass(df[string1], df[string2]) 会返回相同的结果,但在多进程中,前者会对全局的 df 进行多次低效的复制,而后者只会复制每次调用函数所需的列。

撰写回答