dict.__setitem__(key, x) 是否比 dict[key] = x 慢(或快),为什么?

Pho*_*bia 4 python performance dictionary

我发现了一些奇怪的东西。

我定义了两个测试函数:

def with_brackets(n=10000):
    d = dict()
    for i in range(n):
        d["hello"] = i

def with_setitem(n=10000):
    d = dict()
    st = d.__setitem__
    for i in range(n):
        st("hello", i)

Run Code Online (Sandbox Code Playgroud)

人们会期望这两个函数的执行速度大致相同。然而:

>>> timeit(with_brackets, number=1000)
0.6558860000222921

>>> timeit(with_setitem, number=1000)
0.9857697170227766
Run Code Online (Sandbox Code Playgroud)

我可能错过了一些东西,但似乎 setitem 的长度几乎是原来的两倍,我真的不明白为什么。dict[key] = x 不是应该调用 __setitem__ 吗?

(使用 CPython 3.9)

编辑:使用 timeit 而不是 time

met*_*ter 6

不是dict[key] = x应该打电话__setitem__吗?

严格来说,没有。通过 运行你的两个函数dis.dis,我们得到(我只包括for循环):

>>> dis.dis(with_brackets)
...
        >>   22 FOR_ITER                12 (to 36)
             24 STORE_FAST               3 (i)

  5          26 LOAD_FAST                0 (n)
             28 LOAD_FAST                1 (d)
             30 LOAD_CONST               1 ('hello')
             32 STORE_SUBSCR
             34 JUMP_ABSOLUTE           22
...
Run Code Online (Sandbox Code Playgroud)

对比

>>> dis.dis(with_setitem)
...
        >>   28 FOR_ITER                14 (to 44)
             30 STORE_FAST               4 (i)

  6          32 LOAD_FAST                2 (setitem)
             34 LOAD_CONST               1 ('hello')
             36 LOAD_FAST                0 (n)
             38 CALL_FUNCTION            2
             40 POP_TOP
             42 JUMP_ABSOLUTE           28
...
Run Code Online (Sandbox Code Playgroud)

的使用__setitem__涉及函数调用(请参阅CALL_FUNCTIONandPOP_TOP而不是仅仅STORE_SUBSCR- 这是引擎盖下的区别),并且函数调用确实增加了一些开销,因此使用括号访问器会导致更优化的操作码。

  • 或许?您将必须运行一些测试,即使使用括号访问器生成的字节码也将保留为“STORE_SUBSCR”,因为编译器不会区分。但是,鉴于最终将调用自定义“__setitem__”,它可能会导致额外的开销。 (2认同)