如何加速嵌套列表迭代?附多进程无效问题咨询
我有两个各包含10万条文本数据的嵌套列表,需要按特定条件把部分字符串添加到新字典中。但因为列表数据量太大,嵌套迭代的速度慢得离谱。我试过用生成器重写脚本、用map()、用多进程池,但程序运行速度根本没得到实质性提升。现在想请教两个问题:
- 怎么优化代码提升运行速度?
- 为什么多进程池在这个程序里效率这么低?
原代码示例:
list1 = [[['london is capital of uk'],['ny is city in usa'],[...]],[[...],[...],[...]]] list2 = [[['word1 word2 word3'],[...],[...]],[[...],[...],[...]]] def comparing(list1, list2): for orig_index_line1, orig_line1 in enumerate(list1): for orig_index_line2, orig_line2 in enumerate(list2): for new_line1 in list1[orig_index_line1]: for new_line2 in list2[orig_index_line2]: if myfunc_compare(new_line1, new_line2) == 1: new_dict.append(original_list1[orig_index_line1]) break
一、代码优化方案
原代码的核心问题是四层嵌套循环带来的O(MNK*L)时间复杂度,这对10万级数据完全不可行,必须从数据结构和逻辑上重构,而不是只改循环写法。
1. 先扁平化+预处理数据,把查找变快
原列表是三层嵌套结构,先把list2里所有要匹配的文本提取出来,放到集合或哈希表里,把原本O(n)的查找变成O(1):
# 扁平化list2,提取所有文本(假设每个内层元素是单元素列表) flat_list2 = set() for outer_sublist in list2: for inner_item in outer_sublist: flat_list2.add(inner_item[0])
如果myfunc_compare不是简单相等匹配(比如文本包含、相似度匹配),那提前对list2的文本做预处理:比如分词、生成TF-IDF向量,避免每次匹配重复计算。如果是相似度匹配,直接用FAISS、Annoy这类向量索引库建立索引,快速召回候选,不用遍历全量数据。
2. 重构匹配逻辑,砍掉无效嵌套
原代码的逻辑是“遍历list1每个外层元素→遍历list2每个外层元素→遍历两者内层元素匹配”,其实可以简化成:对list1的每个外层元素,检查它的任意内层文本是否能和list2的任意内层文本匹配,匹配到就加入结果。优化后直接砍掉两层嵌套:
def optimized_compare(list1, list2): # 预处理list2 flat_list2 = set() for outer in list2: for inner in outer: flat_list2.add(inner[0]) result = [] for outer_item in list1: matched = False for inner_item in outer_item: text = inner_item[0] # 这里替换成你的myfunc_compare逻辑,比如如果是简单匹配直接用in if myfunc_compare(text, flat_list2): matched = True break if matched: result.append(outer_item) return result
3. 死磕myfunc_compare的性能
如果myfunc_compare是性能瓶颈,单独优化它:
- 用内置字符串方法代替正则(比如
'xxx' in text比re.search('xxx', text)快得多) - 必须用正则的话,提前编译正则表达式(
pattern = re.compile(r'xxx'),之后用pattern.search()) - 重复计算的结果用
functools.lru_cache缓存(注意参数要可哈希,比如把字符串作为参数)
4. 向量化运算(适合数值化文本)
如果能把文本转换成数值向量(比如TF-IDF、Word2Vec),用numpy/pandas做向量化运算,比纯Python循环快几个数量级。比如用sklearn的TfidfVectorizer把所有文本转换成矩阵,然后用矩阵乘法快速找到匹配项。
二、多进程池效率低下的原因
1. 进程间通信开销压过了并行收益
Python多进程需要通过pickle序列化/反序列化传递数据,你的两个列表都是10万级的嵌套结构,把这么大的数据传给子进程的开销,甚至比子进程实际计算的时间还长。尤其是如果任务拆分得很细,每个进程都要拷贝大量数据,完全抵消了并行的优势。
2. 任务拆分不合理
如果只是把最外层循环的每个item分给进程,但每个item的计算量很小,进程创建、调度的开销会远大于实际计算时间。另外原代码的四层嵌套,多进程如果只套在最外层,内层还是串行,提升效果微乎其微。
3. 重复计算+内存浪费
原代码中每个list1的外层元素都要遍历整个list2,用多进程的话,每个进程都要加载一份list2的拷贝,内存直接爆炸,而且大量重复计算相同的list2数据。
4. 对多进程的适用场景误解
多进程适合CPU密集型、任务独立且数据传递开销小的场景。如果你的myfunc_compare是IO密集型(比如读文件),多线程反而更合适;如果是CPU密集型,但数据传递成本太高,多进程也起不到作用。
内容的提问来源于stack exchange,提问作者piolvi

