如果在 python 中更改编译和查找的顺序,为什么性能会有所不同
why the performance different if change the order of compile and findall in python
我注意到通过编译模式进行预处理会加快匹配操作,就像下面的例子。
python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "t.findall('abc eft123&aaa123')"
1000000 loops, best of 3: 1.42 usec per loop
python3 -m timeit -s "import re;" "re.findall(r'[\w+][\d]+', 'abc eft123&aaa123')"
100000 loops, best of 3: 2.45 usec per loop
但是如果我改变编译模式和重新模块的顺序,结果不同,现在似乎慢了很多,为什么会这样?
python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "re.findall(t, 'abc eft123&aaa123')"
100000 loops, best of 3: 3.66 usec per loop
通过 "changing the order",您实际上是在使用 "static" 形式的 findall
,几乎等同于调用 str.lower('ABC')
而不是 'ABC'.lower()
。
根据您使用的 Python 解释器的具体实现,这可能会导致一些开销(例如方法查找)。
换句话说,这更多地与 Python 的工作方式有关,而不是专门针对正则表达式或 re
模块。
from timeit import Timer
def a():
str.lower('ABC')
def b():
'ABC'.lower()
print(min(Timer(a).repeat(5000, 5000)))
print(min(Timer(b).repeat(5000, 5000)))
产出
0.001060427000000086 # str.lower('ABC')
0.0008686820000001205 # 'ABC'.lower()
假设 word1、word2 ... 是正则表达式:
让我们重写这些部分:
allWords = [re.compile(m) for m in ["word1", "word2", "word3"]]
我将为所有模式创建一个正则表达式:
allWords = re.compile("|".join(["word1", "word2", "word3"])
支持正则表达式 |在其中,您必须将表达式括起来:
allWords = re.compile("|".join("({})".format(x) for x in ["word1", "word2", "word3"])
(这当然也适用于标准词,并且由于 | 部分的原因仍然值得使用正则表达式)
现在这是一个伪装的循环,每个术语都被硬编码:
def bar(data, allWords):
if allWords[0].search(data) != None:
temp = data.split("word1", 1)[1] # that works only on non-regexes BTW
return(temp)
elif allWords[1].search(data) != None:
temp = data.split("word2", 1)[1]
return(temp)
可以简单地改写为
def bar(data, allWords):
return allWords.split(data,maxsplit=1)[1]
性能方面:
正则表达式是在开始时编译的,所以它尽可能快
没有循环或粘贴的表达式,"or" 部分由正则表达式引擎完成,大部分时间是一些编译代码:无法在纯 python 中击败它。
匹配和拆分在一次操作中完成
最后一个小问题是正则表达式引擎在内部搜索循环中的所有表达式,这使得它成为一个 O(n) 算法。为了让它更快,你必须预测哪个模式是最常见的,并将它放在第一位(我的假设是正则表达式是 "disjoint",这意味着一个文本不能被多个匹配,否则最长的会必须在较短的之前)
我花了一些时间研究了re.findall
和re.match
的实现,这里复制了标准库源码
def findall(pattern, string, flags=0):
"""Return a list of all non-overlapping matches in the string.
If one or more capturing groups are present in the pattern, return
a list of groups; this will be a list of tuples if the pattern
has more than one group.
Empty matches are included in the result."""
return _compile(pattern, flags).findall(string)
def match(pattern, string, flags=0):
"""Try to apply the pattern at the start of the string, returning
a match object, or None if no match was found."""
return _compile(pattern, flags).match(string)
def _compile(pattern, flags):
# internal: compile pattern
try:
p, loc = _cache[type(pattern), pattern, flags]
if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
return p
except KeyError:
pass
if isinstance(pattern, _pattern_type):
if flags:
raise ValueError(
"cannot process flags argument with a compiled pattern")
return pattern
if not sre_compile.isstring(pattern):
raise TypeError("first argument must be string or compiled pattern")
p = sre_compile.compile(pattern, flags)
if not (flags & DEBUG):
if len(_cache) >= _MAXCACHE:
_cache.clear()
if p.flags & LOCALE:
if not _locale:
return p
loc = _locale.setlocale(_locale.LC_CTYPE)
else:
loc = None
_cache[type(pattern), pattern, flags] = p, loc
return p
这表明,如果我们直接执行re.findall(compiled_pattern, string),它会触发额外调用_compile(pattern, flags),在这个函数中它会做一些检查和在缓存字典中搜索模式。但是,如果我们改为调用 compile_pattern.findall(string)
,则 'additional operation' 将不存在。所以 compile_pattern.findall(string)
会比 re.findall(compile_pattern, string)
快
我注意到通过编译模式进行预处理会加快匹配操作,就像下面的例子。
python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "t.findall('abc eft123&aaa123')"
1000000 loops, best of 3: 1.42 usec per loop
python3 -m timeit -s "import re;" "re.findall(r'[\w+][\d]+', 'abc eft123&aaa123')"
100000 loops, best of 3: 2.45 usec per loop
但是如果我改变编译模式和重新模块的顺序,结果不同,现在似乎慢了很多,为什么会这样?
python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "re.findall(t, 'abc eft123&aaa123')"
100000 loops, best of 3: 3.66 usec per loop
通过 "changing the order",您实际上是在使用 "static" 形式的 findall
,几乎等同于调用 str.lower('ABC')
而不是 'ABC'.lower()
。
根据您使用的 Python 解释器的具体实现,这可能会导致一些开销(例如方法查找)。
换句话说,这更多地与 Python 的工作方式有关,而不是专门针对正则表达式或 re
模块。
from timeit import Timer
def a():
str.lower('ABC')
def b():
'ABC'.lower()
print(min(Timer(a).repeat(5000, 5000)))
print(min(Timer(b).repeat(5000, 5000)))
产出
0.001060427000000086 # str.lower('ABC')
0.0008686820000001205 # 'ABC'.lower()
假设 word1、word2 ... 是正则表达式:
让我们重写这些部分:
allWords = [re.compile(m) for m in ["word1", "word2", "word3"]]
我将为所有模式创建一个正则表达式:
allWords = re.compile("|".join(["word1", "word2", "word3"])
支持正则表达式 |在其中,您必须将表达式括起来:
allWords = re.compile("|".join("({})".format(x) for x in ["word1", "word2", "word3"])
(这当然也适用于标准词,并且由于 | 部分的原因仍然值得使用正则表达式)
现在这是一个伪装的循环,每个术语都被硬编码:
def bar(data, allWords):
if allWords[0].search(data) != None:
temp = data.split("word1", 1)[1] # that works only on non-regexes BTW
return(temp)
elif allWords[1].search(data) != None:
temp = data.split("word2", 1)[1]
return(temp)
可以简单地改写为
def bar(data, allWords):
return allWords.split(data,maxsplit=1)[1]
性能方面:
正则表达式是在开始时编译的,所以它尽可能快 没有循环或粘贴的表达式,"or" 部分由正则表达式引擎完成,大部分时间是一些编译代码:无法在纯 python 中击败它。 匹配和拆分在一次操作中完成 最后一个小问题是正则表达式引擎在内部搜索循环中的所有表达式,这使得它成为一个 O(n) 算法。为了让它更快,你必须预测哪个模式是最常见的,并将它放在第一位(我的假设是正则表达式是 "disjoint",这意味着一个文本不能被多个匹配,否则最长的会必须在较短的之前)
我花了一些时间研究了re.findall
和re.match
的实现,这里复制了标准库源码
def findall(pattern, string, flags=0):
"""Return a list of all non-overlapping matches in the string.
If one or more capturing groups are present in the pattern, return
a list of groups; this will be a list of tuples if the pattern
has more than one group.
Empty matches are included in the result."""
return _compile(pattern, flags).findall(string)
def match(pattern, string, flags=0):
"""Try to apply the pattern at the start of the string, returning
a match object, or None if no match was found."""
return _compile(pattern, flags).match(string)
def _compile(pattern, flags):
# internal: compile pattern
try:
p, loc = _cache[type(pattern), pattern, flags]
if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
return p
except KeyError:
pass
if isinstance(pattern, _pattern_type):
if flags:
raise ValueError(
"cannot process flags argument with a compiled pattern")
return pattern
if not sre_compile.isstring(pattern):
raise TypeError("first argument must be string or compiled pattern")
p = sre_compile.compile(pattern, flags)
if not (flags & DEBUG):
if len(_cache) >= _MAXCACHE:
_cache.clear()
if p.flags & LOCALE:
if not _locale:
return p
loc = _locale.setlocale(_locale.LC_CTYPE)
else:
loc = None
_cache[type(pattern), pattern, flags] = p, loc
return p
这表明,如果我们直接执行re.findall(compiled_pattern, string),它会触发额外调用_compile(pattern, flags),在这个函数中它会做一些检查和在缓存字典中搜索模式。但是,如果我们改为调用 compile_pattern.findall(string)
,则 'additional operation' 将不存在。所以 compile_pattern.findall(string)
会比 re.findall(compile_pattern, string)