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

为什么使用属性而不是方法可以显著提高Python的速度,python,regex,performance,Python,Regex,Performance,我一直在尝试一个做模式匹配的类。我的班级看起来像这样: class Matcher(object): def __init__(self, pattern): self._re = re.compile(pattern) def match(self, value): return self._re.match(value) ncalls tottime percall cumtime percall filename:lineno(function)

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

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

  def match(self, value):
    return self._re.match(value)
   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)
总之,我的脚本运行大约需要45秒。作为实验,我将代码更改为:

class Matcher(object):
  def __init__(self, pattern):
    self._re = re.compile(pattern)
    self.match = self._re.match
运行此脚本花费了37秒。无论我重复这个过程多少次,我都能看到同样显著的性能提升。通过cProfile运行它可以显示如下内容:

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

  def match(self, value):
    return self._re.match(value)
   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)
到底为什么match方法会在运行时间上增加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")
输出:

$ python speedtest.py 
Testing with a method...Done: 50.6646981239
Testing with an attribute...Done: 35.5526258945
作为参考,使用pyp时两者都快得多,但使用阿曲布他而不是使用其他方法时仍显示出显著的收益:

$ pypy speedtest.py 
Testing with a method...Done: 6.15996003151
Testing with an attribute...Done: 3.57215714455

第一个版本是每次额外调用一个函数。这会产生一些开销。

可能主要是额外函数调用的开销。调用Python函数在性能方面相对昂贵,因为需要设置额外的堆栈框架等。下面是一个简单的示例,演示了类似的性能:

>>> timeit.timeit("f()", "g = (lambda: 1); f = lambda: g()")
0.2858083918486847
>>> timeit.timeit("f()", "f = lambda: 1")
0.13749289364989004

在方法内部执行两个额外的属性查找还需要额外的成本:在
self
上查找
\u re
,然后在该
\u re
对象上查找
匹配。然而,这可能是一个较小的组件,因为Python中的字典查找速度非常快。(上面我的
timeit
示例显示,即使在双调用版本中只有一个额外的名称查找,性能也相当差。)

(+1)回答不错——比我的好:)。而且这不仅仅是函数调用。正如
dis
将要确认的那样,即使对于简单的委托,实际上也有几个字节码操作。这些必须被解释。虚拟机不能自动以任何有意义的方式对此进行优化吗?我不赞成直接公开regex.match,但这是我希望避免的巨大性能损失。@davemankoff一个跟踪JIT编译器将这样的代码内联起来作为早餐,所以让PyPy试试。但是CPython,我想你所说的“虚拟机”,不能优化它。它是一个简单的解释器——一个相当优化的解释器,但仍然是一个解释器。@davemankoff:由于Python的动态特性,一般来说很难进行优化。例如,您可以稍后执行
self.\u re=someOtherObject
,然后执行
self.\u re.match
将不再与直接调用正则表达式对象上的match相同。这是通过跟踪实际执行路径并对其进行优化来实现的,但它可能不适合于实际的应用程序,因为它不适用于用C编写的Python扩展。只是为了缓存已编译的模式吗?这似乎太过分了。执行
Matcher=re.compile
而不是创建这个类会有同样的效果,不是吗?@StevenRumbalski这个类实际上有更多的功能。我只展示了一件影响性能的作品。在这种情况下,如果它只是返回True或False,那么它会更有用。实际上,我已经尝试用一些不同的模式匹配算法来代替正则表达式。我在代码中没有看到任何属性……对于那些想知道@kindall的人来说,在Python中,
properties
attributes
指的是不同的东西。在许多语言中,这种区别是模糊的,甚至是毫无意义的,Python保留了动态语言特性的
属性
,它允许透明地使用getter、setter和deleter。我在上面实际使用的
属性是指一个简单的静态对象成员。您使用的术语“属性”也误导了我。虽然不想让人讨厌,但考虑到您正在专门询问一个关于Python的问题,并且您承认在Python上下文中使用术语“properties”具有误导性,为什么不编辑您的问题以使用误导性较小的术语呢?