python-3.x 如果start参数是自定义类的示例,为什么sum函数会更慢?

uwopmtnx  于 2023-11-20  发布在  Python
关注(0)|答案(2)|浏览(98)

我正在使用sum函数,并观察到以下行为。

  • 案例一:*
source = """
class A:
    def __init__(self, a):
        self.a = a
    
    def __add__(self, other):
        return self.a + other;

sum([*range(10000)], start=A(10))
"""

import timeit
print(timeit.timeit(stmt=source))

字符串
正如你所看到的,我使用了一个自定义类的示例作为sum函数的start参数。在我的系统中,对上面的代码进行基准测试大约需要192.60747704200003秒。

  • 案例二:*
source = """
class A:
    def __init__(self, a):
        self.a = a
    
    def __add__(self, other):
        return self.a + other;

sum([*range(10000)], start=10).  <- Here
"""

import timeit
print(timeit.timeit(stmt=source))


但是如果我删除自定义类示例并直接使用int对象,则只需要111.48285191600007秒。我很好奇地想了解这种速度差异的原因?

我的系统信息:

>>> import platform
>>> platform.platform()
'macOS-12.5-arm64-arm-64bit'
>>> import sys
>>> sys.version
'3.11.0 (v3.11.0:deaf509e8f, Oct 24 2022, 14:43:23) [Clang 13.0.0 (clang-1300.0.29.30)]'

vawmfj5a

vawmfj5a1#

builtin_sum_impl内部有两个实现,一个是start是一个数字,它跳过了创建python“数字对象”,只是在C中对数字求和。
start不是一个数字时,另一个较慢的实现会强制调用“number objects”的__add__方法(因为它假设你正在对一些奇怪的类求和)。
你强迫它用慢一点的

bd1hkmkf

bd1hkmkf2#

也许查看字节码可以帮助理解发生了什么。

import dis

def test_range():
    class A:
        def __init__(self, a):
            self.a = a

        def __add__(self, other):
            return self.a + other

    sum([*range(10000)], start=10)

dis.dis(test_range)

字符串
带有start=A(10)的版本会生成2个以上的指令:

2 LOAD_CONST               1 (<code object A at 0x7ff0bfa25c90, file "/.../main.py", line 5>)
...
26 LOAD_CONST               4 (10)
28 LOAD_CONST               5 (('start',))
30 CALL_FUNCTION_KW         2
32 POP_TOP
34 LOAD_CONST               0 (None)
36 RETURN_VALUE


vs

2 LOAD_CONST               1 (<code object A at 0x7ff0bfa25c90, file "/.../main.py", line 5>)
...
26 LOAD_FAST                0 (A)       <--- here
28 LOAD_CONST               4 (10)
30 CALL_FUNCTION            1           <--- and here
32 LOAD_CONST               5 (('start',))
34 CALL_FUNCTION_KW         2
36 POP_TOP
38 LOAD_CONST               0 (None)
40 RETURN_VALUE


start=A(10)版本的完整字节码是here
我(有限的)理解是这两行指向A的初始化。请有人确认。

相关问题