Python:OOP开销?
我一直在开发一个实时应用程序,并注意到一些OOP设计模式在Python中引入了难以置信的开销(用2.7.5测试) 直截了当地说,当字典被另一个对象封装时,为什么字典值的简单访问器方法要多花5倍的时间 例如,运行下面的代码,我得到:Python:OOP开销?,python,performance,oop,Python,Performance,Oop,我一直在开发一个实时应用程序,并注意到一些OOP设计模式在Python中引入了难以置信的开销(用2.7.5测试) 直截了当地说,当字典被另一个对象封装时,为什么字典值的简单访问器方法要多花5倍的时间 例如,运行下面的代码,我得到: Dict Access: 0.167706012726 Attribute Access: 0.191128969193 Method Wrapper Access: 0.711422920227 Property Wrapper Access: 0.93229103
Dict Access: 0.167706012726
Attribute Access: 0.191128969193
Method Wrapper Access: 0.711422920227
Property Wrapper Access: 0.932291030884
可执行代码:
class Wrapper(object):
def __init__(self, data):
self._data = data
@property
def id(self):
return self._data['id']
@property
def name(self):
return self._data['name']
@property
def score(self):
return self._data['score']
class MethodWrapper(object):
def __init__(self, data):
self._data = data
def id(self):
return self._data['id']
def name(self):
return self._data['name']
def score(self):
return self._data['score']
class Raw(object):
def __init__(self, id, name, score):
self.id = id
self.name = name
self.score = score
data = {'id': 1234, 'name': 'john', 'score': 90}
wp = Wrapper(data)
mwp = MethodWrapper(data)
obj = Raw(data['id'], data['name'], data['score'])
def dict_access():
for _ in xrange(100):
uid = data['id']
name = data['name']
score = data['score']
def method_wrapper_access():
for _ in xrange(100):
uid = mwp.id()
name = mwp.name()
score = mwp.score()
def property_wrapper_access():
for _ in xrange(100):
uid = wp.id
name = wp.name
score = wp.score
def object_access():
for _ in xrange(100):
uid = obj.id
name = obj.name
score = obj.score
import timeit
print 'Dict Access:', timeit.timeit("dict_access()", setup="from __main__ import dict_access", number=10000)
print 'Attribute Access:', timeit.timeit("object_access()", setup="from __main__ import object_access", number=10000)
print 'Method Wrapper Access:', timeit.timeit("method_wrapper_access()", setup="from __main__ import method_wrapper_access", number=10000)
print 'Property Wrapper Access:', timeit.timeit("property_wrapper_access()", setup="from __main__ import property_wrapper_access", number=10000)
这是因为Python解释器(CPython)正在进行动态查找,以分派所有调用、索引等。动态查找允许语言具有很大的灵活性,但要以性能为代价。使用“方法包装器”时,至少会发生以下情况:
- 查找
-它恰好是一个方法,但它也只是一个分配给属性的对象,必须像其他对象一样进行查找mwp.id
- 调用
mwp.id()
- 在方法内部,查找
self.\u数据
- 查找
自身数据的
。\u数据\uu获取项目
- 调用
(这至少是一个C函数,但您仍然需要通过所有这些动态查找才能到达此处)\uuuu getitem\uuuuu
\uu getitem\uu
,然后调用它
正如Matteo Italia在评论中指出的,这是特定于实现的。在Python生态系统中,现在您也有PyPy(使用JIT和运行时优化)、Cython(编译到C,带有可选的静态类型注释等)、Nuitka(编译为C++,应该按原样取代码)和多个其他实现。
在CPython上的“纯”Python中优化这些查找的一种方法是获取对对象的直接引用,并将它们分配给循环外部的局部变量,然后在循环内部使用局部变量。这是一个优化,可能会以混乱代码和/或破坏封装为代价。+1,这是一个有趣的问题,但我认为很少有必要过多地考虑CPython的性能-一旦你使用CPython,你已经失去了性能训练。顺便说一句,使用PyPy Dict,方法和属性包装器访问占用的时间是相同的(这大约是我机器上Cpyton的Dict访问时间的一半),属性访问速度大约是Cpyton的Dict的6倍(即比Cpyton的Dict快12倍)。感谢Jason的输入。除了一个巨蟒迷,我不得不承认,当我注意到这一点时,我有点失望。但是,是的,巨大的灵活性是有代价的。