对于您正在尝试的那种事情(在一大堆其他字符串中搜索一组固定的一大堆字符串),并行化和微小的调整不会有太大帮助。您需要算法改进。
首先,我建议使用Aho-Corasick string matching algorithm。基本上,作为从您的一组固定字符串构建匹配器对象的一些预计算工作的交换,您可以一次扫描另一个字符串以查找 all 这些固定字符串。
因此,您无需每次扫描 60K 字符串 50K+ 次(30 亿次扫描?!?),您只需比普通单次扫描略高一点的成本,每次扫描一次,并获得所有命中。
最好的部分是,你不是自己写的。 PyPI(Python 包索引)已经为您编写了 pyahocorasick 包。所以试试吧。
使用示例:
import ahocorasick
listStrings = [ACDE, CDDE, BPLL, ...]
listSubstrings = [ACD, BPI, KLJ, ...]
auto = ahocorasick.Automaton()
for substr in listSubstrings:
auto.add_word(substr, substr)
auto.make_automaton()
...
for astr in listStrings:
for end_ind, found in auto.iter(astr):
w.write(found+astr)
如果在被搜索的字符串(“haystack”)中多次找到子字符串(“needle”),这将write 多次。您可以更改循环,通过使用set 重复数据删除,使其仅在给定大海捞针中的给定针头第一次命中时使用write:
for astr in listStrings:
seen = set()
for end_ind, found in auto.iter(astr):
if found not in seen:
seen.add(found)
w.write(found+astr)
您可以进一步调整它,通过将单词的索引存储为它们的值或与它们的值一起以它们出现在 listSubstrings 中的相同顺序输出给定干草堆的针(并且在您使用时是唯一的),以便您可以对命中进行排序(可能是小数字,所以排序开销很小):
from future_builtins import map # Only on Py2, for more efficient generator based map
from itertools import groupby
from operator import itemgetter
auto = ahocorasick.Automaton()
for i, substr in enumerate(listSubstrings):
# Store index and substr so we can recover original ordering
auto.add_word(substr, (i, substr))
auto.make_automaton()
...
for astr in listStrings:
# Gets all hits, sorting by the index in listSubstrings, so we output hits
# in the same order we theoretically searched for them
allfound = sorted(map(itemgetter(1), auto.iter(astr)))
# Using groupby dedups already sorted inputs cheaply; the map throws away
# the index since we don't need it
for found, _ in groupby(map(itemgetter(1), allfound)):
w.write(found+astr)
为了进行性能比较,我使用了 mgc 答案的变体,该变体更可能包含匹配项,并扩大了干草堆。一、设置代码:
>>> from random import choice, randint
>>> from string import ascii_uppercase as uppercase
>>> # 5000 haystacks, each 1000-5000 characters long
>>> listStrings = [''.join([choice(uppercase) for i in range(randint(1000, 5000))]) for j in range(5000)]
>>> # ~1000 needles (might be slightly less for dups), each 3-12 characters long
>>> listSubstrings = tuple({''.join([choice(uppercase) for i in range(randint(3, 12))]) for j in range(1000)})
>>> auto = ahocorasick.Automaton()
>>> for needle in listSubstrings:
... auto.add_word(needle, needle)
...
>>> auto.make_automaton()
现在来实际测试它(使用 ipython %timeit 魔术进行微基准测试):
>>> sum(needle in haystack for haystack in listStrings for needle in listSubstrings)
80279 # Will differ depending on random seed
>>> sum(len(set(map(itemgetter(1), auto.iter(haystack)))) for haystack in listStrings)
80279 # Same behavior after uniquifying results
>>> %timeit -r5 sum(needle in haystack for haystack in listStrings for needle in listSubstrings)
1 loops, best of 5: 9.79 s per loop
>>> %timeit -r5 sum(len(set(map(itemgetter(1), auto.iter(haystack)))) for haystack in listStrings)
1 loops, best of 5: 460 ms per loop
因此,对于在 5000 个中等大小的字符串中的每一个中检查 ~1000 个较小的字符串,pyahocorasick 在我的机器上以 ~21 倍的倍数击败了个人成员资格测试。随着listSubstrings 的大小增加,它也可以很好地扩展;当我以同样的方式初始化它,但使用 10,000 个小字符串而不是 1000 个时,所需的总时间从约 460 毫秒增加到约 852 毫秒,这是执行 10 倍逻辑搜索的 1.85 倍时间乘数。
为了记录,在这种情况下构建自动机的时间是微不足道的。您预先支付一次,而不是每个 haystack 一次,测试显示约 1000 个字符串自动机需要约 1.4 毫秒的时间来构建并占用约 277 KB 的内存(超出字符串本身);约 10000 个字符串自动机花费了约 21 毫秒的时间来构建,并占用了约 2.45 MB 的内存。