【问题标题】:the cost of rewrite __setattr__ () was too high重写 __setattr__ () 的成本太高
【发布时间】:2012-10-23 03:05:09
【问题描述】:

我想节省时间并将对象标记为已修改,因此我编写了一个类并覆盖其__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)提取集合修改函数

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% 的时间,尽管成本仍然很高。

【问题讨论】:

  • 感谢您的数字反馈!我们可以争辩说涉及两个函数调用,并且由于删除一个将开销减少了 50%,因此您可能会被剩余的开销所困扰。但也许其他人有更多的想法。

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


【解决方案1】:

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

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

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

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

让我们知道每一项的时间变化情况。我把实验分开了。

编辑:感谢您提供的计时数字。

开销可能看起来很大(看起来仍然是 10 倍)。但是,从整体运行时间的角度来看。换句话说:在设置这些被跟踪的属性上会花费多少整体运行时间,以及在其他地方花费多少时间?

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

【讨论】:

  • 这是我目前能得到的最好的改进,我想,我会继续下一步,谢谢。
【解决方案2】:

这里覆盖__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

【讨论】:

  • CacheObject是一个超类,它的子类有各种变量要存储,它们的机制都是一样的。
  • @Max:那么你就隐藏了子类发生的魔法。我将每个属性改为一个属性,并简单地在这些属性中设置访问/修改。这是重复,但不神奇的重复。它不会咬你的脚。
  • 你的意思是使用@property 和setter 吗?这个我试过了,时间长达4.9s
  • @Max:属性设置的放缓可能是不可避免的,除非你用 C 编写你的类。但是,我测试了一下,确实,你的 settattr 比简单的属性设置慢了大约 20 倍。然而,使用属性只慢了 15 倍。请记住,您不必创建 modified 和 lastAccess 的属性,因为它们仅在内部使用。
【解决方案3】:

老问题,但值得更新。

我在使用 python 3.6 时遇到了与 pydantic 相同的问题。

object.__setattr__(self, name, value) 只是比通常在类上设置属性要慢。没有明显的办法。

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

【讨论】:

    猜你喜欢
    • 2018-11-22
    • 1970-01-01
    • 1970-01-01
    • 2020-04-20
    • 1970-01-01
    • 1970-01-01
    • 2020-06-08
    • 2013-08-14
    • 2017-09-23
    相关资源
    最近更新 更多