使用Multiprocessing.pool.map运行的代码比没有运行慢

时间:2019-01-28 15:19:21

标签: python multiprocessing

此代码模拟加载CSV,对其进行解析并将其加载到pandas数据帧中。我想并行化此问题,以使其运行更快,但我的pool.map实现实际上比串行实现慢。

将csv读为一个大字符串,然后先拆分为几行,然后拆分为值。这是具有重复标题的不规则格式的csv,因此我无法使用pandas read_csv。至少我不知道怎么做。

我的想法是简单地以字符串形式读取文件,将长字符串分成四个部分(每个内核一个),然后分别并行处理每个块。事实证明,这比串行版本要慢。

from multiprocessing import Pool
import datetime
import pandas as pd

def data_proc(raw):
    pre_df_list = list()  
    for item in (i for i in raw.split('\n') if i and not i.startswith(',')):
        if ' ' in item and ',' in item:
            key, freq, date_observation = item.split(' ')
            date, observation = date_observation.split(',')
            pre_df_list.append([key, freq, date, observation])
    return pre_df_list

if __name__ == '__main__':
    raw = '\n'.join([f'KEY FREQ DATE,{i}' for i in range(15059071)]) # instead of loading csv
    start = datetime.datetime.now()
    pre_df_list = data_proc(raw)
    df = pd.DataFrame(pre_df_list, columns=['KEY','FREQ','DATE','VAL'])
    end = datetime.datetime.now()
    print(end - start)

    pool = Pool(processes=4) 

    start = datetime.datetime.now()

    len(raw.split('\n'))
    number_of_tasks = 4
    chunk_size = int((len(raw) / number_of_tasks))

    beginning = 0
    multi_list = list()
    for i in range(1,number_of_tasks+1):
        multi_list.append(raw[beginning:chunk_size*i])
        beginning = chunk_size*i

    results =  pool.imap(data_proc, multi_list)
#    d = results[0]
    pool.close()
    pool.join()

#   I haven'f finished conversion to dataframe since previous part is not working yet
#    df = pd.DataFrame(d, columns=['SERIES_KEY','Frequency','OBS_DATE','val'])
    end = datetime.datetime.now()
    print(end - start)

编辑:在我的笔记本电脑上,串行版本在34秒内完成,并行版本在53秒后完成。当我开始进行此工作时,我最初的假设是,我将能够在4核计算机上将其降低到10-ish秒。

看起来我发布的并行版本从未完成。我将pool.map调用更改为pool.imap,现在它又可以工作了。请注意,它必须从命令行而不是Spyder运行。

1 个答案:

答案 0 :(得分:0)

常规:

多处理并不总是最好的方法。创建和管理新流程以及同步其输出会花费一些开销。在相对简单的情况下,例如解析1.5亿行小文本,使用或不使用多处理器都可以节省大量时间。

还有许多其他混杂变量-计算机上的进程负载,处理器数量,对I / O的任何访问都分布在整个处理器上(在您的特定情况下这不是问题),填充的可能性增加内存,然后处理页面交换...列表可以保持增长。有时,多处理是理想的选择,但有时会使情况变得更糟。 (在我的生产代码中,我在一个地方留下了评论:“在这里使用多处理所花的时间比不花3倍。仅使用常规地图...”)

您的具体情况

但是,在不知道确切的系统规格的情况下,我认为您应该通过正确执行的多处理来提高性能。你不可以;这项任务可能很小,以致不值得您承担这些开销。但是,您的代码存在一些问题,这将导致您的多处理路径花费更长的时间。我会召集那些引起我注意的人

len(raw.split('\n'))

这条线非常昂贵,无济于事。它遍历原始数据的每一行,将其拆分,获取结果的长度,然后抛出拆分的数据和len。您可能想要做类似的事情:

splitted = raw.split('\n')
splitted_len = len(splitted) # but I'm not sure where you need this.

这将保存拆分数据,因此以后可以在for循环中使用它。现在,您的for循环在未分割的原始上运行。因此,您正在[first_part, second_part, third_part, fourth_part]上运行,而不是在[all_of_it, all_of_it, all_of_it, all_of_it]上运行。当然,这是性能下降的巨大部分-您正在做x4的相同工作!

我希望,如果您愿意在处理之外对\n进行拆分,那么您就需要从多处理中获得改进。 (请注意,对于“串行”和“并行”,您实际上不需要任何特殊处理-您可以使用map而不是pool.map进行不错的测试。)

这是我重新执行代码的目的。它将行拆分移出data_proc函数,因此您可以集中精力将数组拆分为4个块是否有任何改善。 (除此之外,它使每个任务成为一个定义明确的函数-只是样式,以帮助澄清在哪里进行测试。)

from multiprocessing import Pool
import datetime
import pandas as pd

def serial(raw):
    pre_df_list = data_proc(raw)
    return pre_df_list

def parallel(raw):
    pool = Pool(processes=4) 

    number_of_tasks = 4
    chunk_size = int((len(raw) / number_of_tasks))

    beginning = 0
    multi_list = list()
    for i in range(1,number_of_tasks+1):
        multi_list.append(raw[beginning:chunk_size*i])
        beginning = chunk_size*i

    results =  pool.map(data_proc, multi_list)
    pool.close()
    pool.join()

    pre_df_list = []
    for r in results:
        pre_df_list.append(r)
    return pre_df_list

def data_proc(raw):
    # assume raw is pre-split by the time you're here
    pre_df_list = list()  
    for item in (i for i in if i and not i.startswith(',')):
        if ' ' in item and ',' in item:
            key, freq, date_observation = item.split(' ')
            date, observation = date_observation.split(',')
            pre_df_list.append([key, freq, date, observation])
    return pre_df_list

if __name__ == '__main__':
    # don't bother with the join, since we would need it in either case
    raw = [f'KEY FREQ DATE,{i}' for i in range(15059071)] # instead of loading csv

    start = datetime.datetime.now()
    pre_df_list = serial(raw)
    end = datetime.datetime.now()
    print("serial time: {}".format(end - start))

    start = datetime.datetime.now()
    pre_df_list = parallel(raw)
    end = datetime.datetime.now()
    print("parallel time: {}".format(end - start))

    # make the dataframe.  This would happen in either case
    df = pd.DataFrame(pre_df_list, columns=['KEY','FREQ','DATE','VAL'])