此代码模拟加载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运行。
答案 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'])