python - 性能差异 : obj. __setitem__(x,y) 与 obj[x] = y?

标签 python performance

我正在编写一个具有属性访问权限的简单 dict 子类,并且在优化时偶然发现了一些看起来很奇怪的东西。我最初将 __getattr____setattr__ 方法编写为 self[key] 等的简单别名,但后来我认为调用它应该更快self.__getitem__self.__setitem__ 直接调用,因为它们可能会被 [key] 符号调用。出于好奇,我对这两个实现进行了计时,并发现了一些惊喜。

下面是两个实现:如您所见,这里没有太多进展。

# brackets
class AttrDict(dict):
    def __getattr__(self, key):
        return self[key]
    def __setattr__(self, key, val):
        self[key] = val

# methods
class AttrDict(dict):
    def __getattr__(self, key):
        return self.__getitem__(key)
    def __setattr__(self, key, val):
        self.__setitem__(key, val)

凭直觉,我预计第二个实现会稍微快一些,因为它大概跳过了从括号表示法进入函数调用的步骤。然而,这并不是我的 timeit 结果所显示的。

>>> methods = '''\
... class AttrDict(dict):
...     def __getattr__(self, key):
...         return self.__getitem__(key)
...     def __setattr__(self, key, val):
...         self.__setitem__(key, val)
... o = AttrDict()
... o.att = 1
... '''
>>> brackets = '''\
... class AttrDict(dict):
...     def __getattr__(self, key):
...         return self[key]
...     def __setattr__(self, key, val):
...         self[key] = val
...
... o = AttrDict()
... o.att = 1
... '''
>>> getting = 'foo = o.att'
>>> setting = 'o.att = 1'

上面的代码只是设置。以下是测试:

>>> for op in (getting, setting):
...     print('GET' if op == getting else 'SET')
...     for setup in (brackets, methods):
...             s = 'Brackets:' if setup == brackets else 'Methods:'
...             print(s, min(timeit.repeat(op, setup, number=1000000, repeat=20)))
...
GET
Brackets: 1.109725879526195
Methods: 1.050940903987339
SET
Brackets: 0.44571820606051915
Methods: 0.7166479863124096
>>>

如您所见,使用 self.__getitem__self[key] 稍微,但是 self.__setitem__ self[key] = val 明显。这看起来很奇怪——我知道函数调用的开销可能很大,但如果那是问题所在我希望在这两种情况下都能更快地看到括号表示法,但这里不会发生这种情况。 p>


我进一步研究了它;以下是 dis 结果:

>>> exec(brackets)
>>> dis.dis(AttrDict.__getattr__)
  3           0 LOAD_FAST                0 (self)
              3 LOAD_FAST                1 (key)
              6 BINARY_SUBSCR
              7 RETURN_VALUE
>>> dis.dis(AttrDict.__setattr__)
  5           0 LOAD_FAST                2 (val)
              3 LOAD_FAST                0 (self)
              6 LOAD_FAST                1 (key)
              9 STORE_SUBSCR
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
>>> exec(methods)
>>> dis.dis(AttrDict.__getattr__)
  3           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (__getitem__)
              6 LOAD_FAST                1 (key)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 RETURN_VALUE
>>> dis.dis(AttrDict.__setattr__)
  5           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (__setitem__)
              6 LOAD_FAST                1 (key)
              9 LOAD_FAST                2 (val)
             12 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             15 POP_TOP
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

我唯一能想到的是,与其余调用相比,POP_TOP 指令的开销可能很大,但它真的那么吗?这是这里唯一突出的东西......任何人都可以看到是什么让 __setitem__ 相对于 __getitem__ 比它的表兄弟慢得多吗?

可能相关的信息:

CPython 3.3.2 32 位win32

最佳答案

嗯,这很有趣。如果我运行你的东西的精简版:

setup="""
def getbrack(a, b):
    return a[b]

def getitem(a, b):
    return a.__getitem__(b)

def setbrack(a, b, c):
    a[b] = c

def setitem(a, b, c):
    return a.__setitem__(b, c)

a = {2: 3}
"""

setitemgetitem 都比它们对应的 setbrackgetbrack 慢:

>>> timeit.timeit("getbrack(a, 2)", setup, number=10000000)
1.1424450874328613
>>> timeit.timeit("getitem(a, 2)", setup, number=10000000)
1.5957350730895996
>>> timeit.timeit("setbrack(a, 2, 3)", setup, number=10000000)
1.4236340522766113
>>> timeit.timeit("setitem(a, 2, 3)", setup, number=10000000)
2.402789831161499

但是,如果我完全运行您的测试,那么我会得到相同的结果 - GET 'Brackets'GET 'Methods' 慢。

这意味着它与您正在使用的类有关,而不是括号与 setitem 本身。


现在,如果我将测试修改为不再引用 self...

brackets = '''d = {}

class AttrDict2(dict):
    def __getattr__(self, key):
        return d[key]
    def __setattr__(self, key, val):
        d[key] = val

o = AttrDict2()
o.att = 1'''

methods = '''d = {}

class AttrDict2(dict):
    def __getattr__(self, key):
        return d.__getitem__(key)
    def __setattr__(self, key, val):
        d.__setitem__(key, val)

o = AttrDict2()
o.att = 1'''

然后我再次得到括号总是比方法快的行为。所以也许它与 self[]dict 子类中的工作方式有关?

关于python - 性能差异 : obj. __setitem__(x,y) 与 obj[x] = y?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21734658/

相关文章:

java - 从 Java BlockingQueue 并发按顺序处理工作项

mysql - 哪个数据库最适合 Magento

windows - 系统性能计数器的实例名称是否已本地化?

python - 使用python在mysql中动态创建表

mysql - 如何在具有高度冗余键值的数据库中加速 mysql select

javascript - 为什么在 JavaScript 中 === 比 == 快?

python - 绘制到 Pycharm 的 IPython 控制台

python - PYmssql python连接字符串

python - 如何在Python中将文件从gml转换为edgelist?

python - Xlsxwriter - 使用 xlsxwriter 格式化 Pandas 数据框单元格时遇到问题