Hen*_*ter 7 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)
Run Code Online (Sandbox Code Playgroud)
直觉上,我预计第二个实现会稍快一些,因为它可能会跳过从括号表示法转换为函数调用的步骤.但是,这并不是我的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'
Run Code Online (Sandbox Code Playgroud)
上面的代码都只是设置.以下是测试:
>>> 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
>>>
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,使用self.__getitem__是非常轻微快比self[key],但self.__setitem__就是显著慢比self[key] = val.这看起来很奇怪 - 我知道函数调用开销可能很大,但如果这是问题,我希望在两种情况下看到括号表示法更快,这在这里没有发生.
我进一步调查了一下; 这里的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
Run Code Online (Sandbox Code Playgroud)
我唯一能想到的是,POP_TOP与其他调用相比,这条指令可能会有很大的开销,但它真的可以那么多吗?这是唯一在这里脱颖而出的东西......任何人都可以看到正在发生的事情__setitem__比它的支架表兄慢得多,相对于__getitem__?
潜在相关信息:
在win32上使用CPython 3.3.2 32位
嗯,这很有趣。如果我运行你的东西的精简版本:
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}
"""
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
但是,如果我完全运行您的测试,那么我会得到与您相同的结果 -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'''
Run Code Online (Sandbox Code Playgroud)
然后我再次得到括号总是比方法更快的行为。self[]那么也许这与子类中的工作方式有关dict?
| 归档时间: |
|
| 查看次数: |
255 次 |
| 最近记录: |