重写__setattr __()的成本太高了

时间:2012-11-02 12:44:48

标签: performance python-3.x double-underscore

我想节省时间并将对象标记为已修改,因此我编写了一个类并覆盖其__setattr__函数。

import time

class CacheObject(object):
    __slots__ = ('modified', 'lastAccess')
    def __init__(self):
        object.__setattr__(self,'modified',False)
        object.__setattr__(self,'lastAccess',time.time())

    def setModified(self):
        object.__setattr__(self,'modified',True)
        object.__setattr__(self,'lastAccess',time.time())

    def resetTime(self):
        object.__setattr__(self,'lastAccess',time.time())

    def __setattr__(self,name,value):
        if (not hasattr(self,name)) or object.__getattribute__(self,name)!=value: 
            object.__setattr__(self,name,value)
            self.setModified()

class example(CacheObject):
    __slots__ = ('abc',)
    def __init__(self,i):
        self.abc = i
        super(example,self).__init__()

t = time.time()
f = example(0)
for i in range(100000):
    f.abc = i

print(time.time()-t)

我测量了处理时间,花了2秒钟。当我注释掉被覆盖的函数时,处理时间是0.1秒,我知道被覆盖的函数会慢一些,但差距太大了近20倍。我想我一定是搞错了。

接受cfi的建议

1.标准if条件

    def __setattr__(self,name,value):
#        if (not hasattr(self,name)) or object.__getattribute__(self,name)!=value: 
            object.__setattr__(self,name,value)
            self.setModified()

运行时间下降到1.9,略有改进,但如果未更改的对象标记为修改,则在其他进程中会花费更多,因此不是一个选项。

2.将self.func更改为classname.func(self)

def __setattr__(self,name,value):
    if (not hasattr(self,name)) or object.__getattribute__(self,name)!=value: 
        object.__setattr__(self,name,value)
        CacheObject.setModified(self)

运行时间是2.0。所以没有真正改变

3)提取setmodified函数

def __setattr__(self,name,value):
    if (not hasattr(self,name)) or object.__getattribute__(self,name)!=value: 
        object.__setattr__(self,name,value)
        object.__setattr__(self,'modified',True)
        object.__setattr__(self,'lastAccess',time.time())

运行时间降至1.2 !!这很好,它确实节省了近50%的时间,但成本仍然很高。

3 个答案:

答案 0 :(得分:1)

不是一个完整的答案,而是一些建议:

  1. 你能否消除价值比较?当然,这是您实现的功能更改。但如果在属性中存储比整数更复杂的对象,那么运行时的开销会更糟。

  2. 通过self对方法的每次调用都需要经过完整的方法解析顺序检查。我不知道Python是否可以进行任何MRO缓存。可能不是因为类型 - 动态原理。因此,您应该可以通过将任何self.method(args)更改为classname.method(self, args)来减少一些开销。这消除了调用中的MRO开销。这适用于self.setModified()实施中的settattr()。在大多数地方,您已经参考了object

  3. 每个函数调用都需要时间。你可以消除它们,例如将setModified的功能移至__setattr__本身。

  4. 让我们知道每个时间的时间变化。我把实验分开了。

    编辑:感谢时间数字。

    开销可能看起来很激烈(似乎仍然是10倍)。然而,将其放入整体运行时的角度。换句话说:在设置这些跟踪属性以及在其他地方花费了多少时间时,您将花费多少总体运行时间?

    在单线程应用程序Amdahl's Law is a simple rule中直接设置期望值。举例说明:如果1/3的时间用于设置属性,2/3用于做其他事情。然后将属性设置减慢10倍只会减慢30%。使用属性花费的时间百分比越小,我们就越不需要关心。但如果你的百分比很高,这可能对你没有帮助......

答案 1 :(得分:0)

覆盖__setattr__这里似乎没有任何功能。您只有两个属性,modified和lastAccess。这意味着这是您可以设置的唯一属性,那么为什么要覆盖__setattr__?只需直接设置属性即可。

如果您想在设置属性时发生某些事情,请将其设为具有setter和getter的属性。它更容易,也不那么神奇。

class CacheObject(object):
    __slots__ = ('modified', 'lastAccess')

    def __init__(self):
        self.modified = False
        self.lastAccess = time.time()

    def setModified(self):
        self.modified = True
        self.lastAccess = time.time()

    def resetTime(self):
        self.lastAccess = time.time()

class example(CacheObject):
    __slots__ = ('_abc',)
    def __init__(self,i):
        self._abc = i
        super(example,self).__init__()

    @property
    def abc(self):
        self.resetTime()
        return self._abc


    @abc.setter
    def abc(self, value):
        self.setModified()
        self._abc = value

答案 2 :(得分:0)

旧问题,但值得更新。

我使用python 3.6与pydantic遇到了同样的问题。

object.__setattr__(self, name, value)只比正常设置类的属性慢。没有明显的方法。

如果效果很重要,唯一的选择是在需要覆盖object.__setattr__(self, name, value)的类中将_setattr_的来电保持在绝对最小值。