我正在编写一个具有属性访问权限的简单 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}
"""
setitem
和 getitem
都比它们对应的 setbrack
和 getbrack
慢:
>>> 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/