为什么在使用多进程时追加列表速度较慢?

Why append list is slower when using multiprocess?

我想追加列表。每个要附加的元素都是一个大数据框。
我尝试使用多处理模块来加速附加列表。我的代码如下:

import pandas as pd
import numpy as np
import time
import multiprocessing

from multiprocessing import Manager

def generate_df(size):
    df = pd.DataFrame()
    for x in list('abcdefghi'):
        df[x] = np.random.normal(size=size)
    return df

def do_something(df_list,size,k):
    df = generate_df(size)
    df_list[k] = df

if __name__ == '__main__':
    size = 200000
    num_df = 30
    start = time.perf_counter()
    with Manager() as manager:
        df_list = manager.list(range(num_df))

        processes = []
        for k in range(num_df):
            p = multiprocessing.Process(target=do_something, args=(df_list,size,k,)) 
        p.start()
        processes.append(p)

    for process in processes:
        process.join()
    
    final_df = pd.concat(df_list)

    print(final_df.head())
    finish = time.perf_counter()
    print(f'Finished in {round(finish-start,2)} second(s)')
    print(len(final_df))

经过的时间是 7 秒。

我尝试在没有多处理的情况下附加列表。

df_list = []
for _ in range(num_df):
    df_list.append(generate_df(size))

final_df = pd.concat(df_list)

但是,这次的耗时是2秒!为什么使用多处理追加列表比没有多处理要慢?

两点:

  • 从子进程启动和检索数据 成本 数据必须在进程之间传输。这意味着如果运输时间多于计算数据所需的时间,您将找不到任何好处。 This article 可以更好地解释问题。
  • 在您的实施中,瓶颈在于 df_list 使用。 The Manager 使用锁,这意味着进程不能自由地将结果写入列表 df_list

当您使用 manager.list 时,您使用的不是普通的 Python 列表。您正在使用一个特殊的列表代理对象,它有很多其他的事情正在进行。该列表上的每个操作都将涉及锁定和进程间通信,以便每个有权访问该列表的进程都将始终看到其中的相同数据。它很慢,因为以这种方式保持一切一致是一个非常重要的问题。

您可能不需要所有这些同步,它只会减慢您的速度。一种更自然的方法来做你正在尝试的事情是使用进程池,它是 map 方法。池将处理进程的创建和关闭,map 将使用可迭代对象的参数调用目标函数。

尝试这样的事情,它会使用与您的系统拥有的 CPU 数量相等的工作进程数:

if __name__ == '__main__':
    size = 200000
    num_df = 30
    start = time.perf_counter()

    with multiprocessing.pool() as pool:
        df_list = pool.map(generate_df, [size]*num_df)

    final_df = pd.concat(df_list)
    print(final_df.head())
    finish = time.perf_counter()
    print(f'Finished in {round(finish-start,2)} second(s)')
    print(len(final_df))

这仍然会有一些开销,因为用于将数据帧传回主进程的进程间通信不是免费的。它可能仍然比 运行 单个进程中的所有内容慢。