多进程处理快速计算
我遇到了一个有趣的多进程问题,可能可以利用它的结构。这个问题涉及一个大约有80列的DataFrame
(df
),里面有很多列,还有一个函数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 个回答
这个结果其实很常见。在并行运行时,没有什么能完美地线性扩展,因为每个进程的设置和进程间数据传递都需要一些额外的开销。要记住,(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
进行多次低效的复制,而后者只会复制每次调用函数所需的列。