为什么使用属性而不是方法可以提高Python速度的显着提升

dav*_*off 7 python regex performance

我一直在尝试一个模式匹配的类.我的班级看起来像这样:

class Matcher(object):
  def __init__(self, pattern):
    self._re = re.compile(pattern)

  def match(self, value):
    return self._re.match(value)
Run Code Online (Sandbox Code Playgroud)

总而言之,我的脚本需要大约45秒才能运行.作为实验,我将代码更改为:

class Matcher(object):
  def __init__(self, pattern):
    self._re = re.compile(pattern)
    self.match = self._re.match
Run Code Online (Sandbox Code Playgroud)

这个脚本的运行耗时37秒.无论我重复这个过程多少次,我都会看到同样显着的性能提升.通过cProfile运行它显示如下:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 46100979   14.356    0.000   14.356    0.000 {method 'match' of '_sre.SRE_Pattern' objects}
 44839409    9.287    0.000   20.031    0.000 matcher.py:266(match)
Run Code Online (Sandbox Code Playgroud)

为什么地球上的匹配方法在运行时增加了9.2秒?最令人沮丧的部分是我试图重新创建一个简单的案例而无法这样做.我在这里错过了什么?我的简单测试用例有一个bug!现在它模仿我所看到的行为:

import re
import sys
import time

class X(object):
  def __init__(self):
    self._re = re.compile('.*a')

  def match(self, value):
    return self._re.match(value)

class Y(object):
  def __init__(self):
    self._re = re.compile('ba')
    self.match = self._re.match

inp = 'ba'
x = X()
y = Y()

sys.stdout.write("Testing with a method...")
sys.stdout.flush()
start = time.time()
for i in range(100000000):
  m = x.match(inp)
end = time.time()
sys.stdout.write("Done: "+str(end-start)+"\n")

sys.stdout.write("Testing with an attribute...")
sys.stdout.flush()
start = time.time()
for i in range(100000000):
  m = y.match(inp)
end = time.time()
sys.stdout.write("Done: "+str(end-start)+"\n")
Run Code Online (Sandbox Code Playgroud)

输出:

$ python speedtest.py 
Testing with a method...Done: 50.6646981239
Testing with an attribute...Done: 35.5526258945
Run Code Online (Sandbox Code Playgroud)

作为参考,都是与PYP较快,但仍表现出与属性附加伤害,而不是一个方法运行时,显著的收益:

$ pypy speedtest.py 
Testing with a method...Done: 6.15996003151
Testing with an attribute...Done: 3.57215714455
Run Code Online (Sandbox Code Playgroud)

Bre*_*arn 8

它可能主要是附加函数调用的开销.调用Python函数在性能方面是相对昂贵的,因为需要设置额外的堆栈帧等.这是一个证明类似性能的简单示例:

>>> timeit.timeit("f()", "g = (lambda: 1); f = lambda: g()")
0.2858083918486847
>>> timeit.timeit("f()", "f = lambda: 1")
0.13749289364989004
Run Code Online (Sandbox Code Playgroud)

也有做两个额外的属性查询你的方法中的额外费用:仰视_reself,然后查找match_re对象.但是,这可能是一个较小的组件,因为字典查找在Python中相当快.(timeit上面的示例显示了相当差的性能,即使在双调用版本中只有一个额外的名称查找.)

  • @davemankoff一个跟踪JIT编译器在早餐时为这样的代码内联,所以试试PyPy吧.但是CPython,我认为你的意思是"vm",不能优化它.这是一个简单的解释器 - 一个相当优化的解释器,但仍然是一个解释器. (3认同)