重写__setattr__()的成本太高了

问题描述 投票:0回答:3

我想节省时间并将对象标记为已修改,所以我编写了一个类并重写其

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

python python-3.x performance
3个回答
1
投票

不是完整的答案,但有一些建议:

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

  2. 每次通过

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

  3. 每个函数调用都需要时间。你可以消除它们,例如将

    setModified
    的功能移至
    __setattr__
    本身。

让我们知道每个项目的时间安排如何变化。我把实验分开了。

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

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

在单线程应用程序中阿姆达尔定律是一个简单的规则,可以直接设定期望。举个例子:如果1/3的时间花在设置属性上,2/3的时间花在其他事情上。那么将属性设置减慢10倍只会减慢30%。花在属性上的时间百分比越小,我们就越不需要关心。但如果您的百分比很高,这可能根本没有帮助您......


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

0
投票

老问题但值得更新。

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

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

如果性能很重要,唯一的选择是在需要覆盖

object.__setattr__(self, name, value)
的类中将对
_setattr_
的调用保持在绝对最低限度。

© www.soinside.com 2019 - 2024. All rights reserved.