Python:OOP开销?

Tre*_*ein 3 python oop performance

我一直在研究实时应用程序,并注意到一些OOP设计模式在Python中引入了令人难以置信的开销(用2.7.5测试).

简单明了,为什么当字典被另一个对象封装时,字典值的简单访问方法需要花费近5倍的时间?

例如,运行下面的代码,我得到:

Dict Access: 0.167706012726
Attribute Access: 0.191128969193
Method Wrapper Access: 0.711422920227
Property Wrapper Access: 0.932291030884
Run Code Online (Sandbox Code Playgroud)

可执行代码:

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)
Run Code Online (Sandbox Code Playgroud)

Jas*_*n S 5

这是因为Python解释器(CPython)正在进行动态查找以调度所有调用,索引等.动态查找允许语言具有很大的灵活性,但性能成本却很高.当您使用"方法包装器"时,这(至少)正在发生:

  • 查找mwp.id- 它恰好是一个方法,但它也只是一个分配给属性的对象,必须像其他任何一样被查找
  • 呼叫 mwp.id()
  • 在方法内部,查找 self._data
  • 抬头看__getitem__self._data
  • 调用__getitem__(这至少将是一个C函数,但你还是必须经过所有这些动态查找到这里)

相比之下,您的"Dict Access"测试用例只需要查找__getitem__然后调用它.

正如Matteo Italia在评论中指出的那样,这是特定于实现的.在Python的生态系统,现在你也有PyPy(使用JIT和运行时优化),用Cython(编译为C,可选静态类型的注释等),Nuitka(编译为C++,应该采取代码-IS),和其他多种实现.

在CPython上"纯"Python中优化这些查找的一种方法是直接引用对象并将它们分配给循环外的局部变量,然后在循环内使用局部变量.这是一种可能以代码混乱和/或破坏封装为代价的优化.