多进程处理因418k化学品字典挂起,寻求性能瓶颈排查方案
多词化学品名称连字符替换代码的性能瓶颈排查
我写了一段代码,用来把特定多词化学品名称里的空格替换成连字符(比如把magnesium oxide替换成magnesium-oxide)。但不管化学品列表长短,用multiprocessing处理都慢得离谱——列表短的时候要跑将近一天,现在列表长度到418k,跑了一天后直接挂起了。想找出导致性能差和挂起的瓶颈。
以下是相关代码和数据示例:
import re import time import numpy import pandas import pickle from datetime import datetime from multiprocessing import Pool # 定义预处理类提升效率 class Hyphenizer: def __init__(self, chem_pattern, chem_name_hyp_dict): # chem_pattern是带单词边界的化学品名称正则联合模式(例如 r'\bmagnesium oxide\b') self.chem_pattern = chem_pattern # chem_name_hyp_dict是多词化学品名称到连字符版本的映射字典,例:{'magnesium oxide': 'magnesium-oxide'} self.chem_name_hyp_dict = chem_name_hyp_dict def hyphenize_chemicals(self, series): hyphenized_text = [] # 将Series转为列表 series_as_list = series.tolist() for text in series_as_list: # 检查文本中是否存在多词化学品 if bool(re.search(self.chem_pattern, text)): # 将匹配到的多词化学品替换为连字符版本 text = self.chem_pattern.sub( lambda match: self.chem_name_hyp_dict[match.group(0)], text) hyphenized_text.append(text) return hyphenized_text if __name__ == '__main__': # 读取清洗后的论文数据,忽略不必要的列 articles = pandas.read_csv("../data/abstracts_clean.csv", error_bad_lines = False, warn_bad_lines = True, usecols = ['doi', 'abstract', 'doc_id', 'clean_abstract']) # 将数据拆分为块,用于并行处理(每块100条文本) chunks = numpy.array_split(articles['clean_abstract'], 71) # 加载带单词边界的多词化学品列表 with open('../data/chem_w_bound_w_filt.pkl', 'rb') as f: chem_name_list_w_boundaries = pickle.load(f) # 编译正则模式 chem_pattern = re.compile("|".join(chem_name_list_w_boundaries)) # 加载多词化学品到连字符版本的映射字典 with open('../data/chem_name_hyp_dict_filt.pkl', 'rb') as f: chem_name_hyp_dict = pickle.load(f) # 存储所有处理后的块 hyphenized_abs_lil = [] # 创建Hyphenizer实例 hyphenizer = Hyphenizer(chem_pattern, chem_name_hyp_dict) # 遍历块并提交到进程池 for i, clean_abs_chunk in enumerate(chunks): # 将块进一步拆分为更小的子块(每个进程处理25条文本) smaller_chunks = numpy.array_split(clean_abs_chunk, 4) # 记录当前时间 now = datetime.now() now_formatted = now.strftime('%H:%M:%S') print('当前时间:{}'.format(now_formatted)) with Pool(4) as pool: hyphenized_abs = pool.map( hyphenizer.hyphenize_chemicals, smaller_chunks) hyphenized_abs_lil.append(hyphenized_abs)
补充说明
数据示例
abstracts_clean.csv包含论文摘要及相关信息,处理目标列clean_abstract示例如下:
articles.clean_abstract.head() 0 sausages are one of the oldest processed foods... 1 populations of pollinating insects are in conc... 2 it was highlighted that the original article c... 3 foodborne pathogens are a major cause of diarr... 4 a sizable proportion of the world population i...
化学品映射字典示例
chem_name_hyp_dict是多词化学品名称到连字符版本的映射字典,示例如下:
list(chem_name_hyp_dict.items())[: 5] [("9beta-d-ribofuranosylpurine cyclic 3',5'-phosphate", "9beta-d-ribofuranosylpurine-cyclic-3',5'-phosphate"), ('2-methoxy-17betaestradiol 3-o-(beta-d-glucuronide)', '2-methoxy-17betaestradiol-3-o-(beta-d-glucuronide)'), ('n(3)-(4-methoxyfumaroyl)-2,3-diaminopropionic acid', 'n(3)-(4-methoxyfumaroyl)-2,3-diaminopropionic-acid'), ('calcium disodium ethylenediamine tetraacetic acids', 'calcium-disodium-ethylenediamine-tetraacetic-acids'), ('trans-1,2-ethylenedicarboxylic acid dimethyl ester', 'trans-1,2-ethylenedicarboxylic-acid-dimethyl-ester')]
瓶颈排查与优化方案
1. 正则表达式的性能灾难
用|拼接418k个带\b边界的正则表达式是核心问题:
- Python正则引擎处理超长分支表达式时,会陷入指数级回溯,尤其是化学品名称有重叠前缀/后缀时,匹配速度暴跌。
\b边界在处理含特殊字符(如'、(、-)的化学品名称时,会出现匹配异常,同时进一步拖慢匹配速度。
2. 多进程的低效使用
- 循环中反复创建
Pool(4),每次创建销毁进程池都会带来巨大开销——进程启动、大对象(正则、字典)的内存拷贝成本极高,并行效率不升反降。 - 拆分的chunk过小(25条文本),进程间任务调度成本远高于实际处理成本。
3. 冗余的匹配检查
先调用re.search再执行sub,等于对同一段文本做两次正则扫描,完全是重复劳动。
优化步骤
第一步:替换正则方案——用Aho-Corasick自动机替代分支正则
面对十万级关键词匹配,Aho-Corasick算法是线性时间复杂度,比正则高效几个数量级,用pyahocorasick库实现:
import ahocorasick import pandas # 定义预处理类提升效率 class Hyphenizer: def __init__(self, chem_name_hyp_dict): self.automaton = ahocorasick.Automaton() # 按名称长度倒序添加,优先匹配长名称(避免短名称截断长名称) for name, hyphenated in sorted(chem_name_hyp_dict.items(), key=lambda x: -len(x[0])): self.automaton.add_word(name, hyphenated) self.automaton.make_automaton() def hyphenize_text(self, text): # 从后往前替换,避免替换后影响前面的匹配位置 offset = 0 for end_idx, hyphenated in self.automaton.iter(text): original_len = len(hyphenated.replace('-', '')) start_idx = end_idx - original_len + 1 text = text[:start_idx+offset] + hyphenated + text[end_idx+1+offset:] offset += len(hyphenated) - original_len return text def hyphenize_chemicals(self, series): return series.apply(self.hyphenize_text).tolist()
安装依赖:
pip install pyahocorasick
第二步:重构多进程逻辑
- 只创建一次进程池,避免反复销毁重建的开销。
- 合理设置chunk大小,每个进程处理几百到几千条文本,平衡调度成本和并行效率。
if __name__ == '__main__': # 只加载需要处理的列 articles = pandas.read_csv("../data/abstracts_clean.csv", error_bad_lines=False, warn_bad_lines=True, usecols=['clean_abstract']) with open('../data/chem_name_hyp_dict_filt.pkl', 'rb') as f: chem_name_hyp_dict = pickle.load(f) hyphenizer = Hyphenizer(chem_name_hyp_dict) # 按CPU核心数设置进程数,每个chunk分配约1000条文本 num_processes = 4 chunks = numpy.array_split(articles['clean_abstract'], num_processes * 10) hyphenized_abs_lil = [] with Pool(num_processes) as pool: # 一次性提交所有chunk results = pool.map(hyphenizer.hyphenize_chemicals, chunks) hyphenized_abs_lil.extend(results) # 合并所有结果 final_hyphenized = [item for sublist in hyphenized_abs_lil for item in sublist]
第三步:其他细节优化
- 若CSV数据量极大,用
chunksize参数分块读取,避免一次性加载占满内存。 - 移除代码中不必要的变量和打印操作,减少额外开销。
内容的提问来源于stack exchange,提问作者newbie
相关产品推荐
相关产品推荐

