如果 'start' 参数是自定义类的实例,为什么 sum 函数会变慢?



我正在玩sum函数,并观察到以下行为:

案例1:

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秒。

案例2:

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)]'

builtin_sum_impl内部有两个实现,如果start是一个数字,则跳过创建python"数字对象";和c中的数字。

是另一种较慢的实现,当start不是数字时,这会强制__add__方法为"数字对象"。要被调用,(因为它假定您正在求和一些奇怪的类)。

你强迫它使用较慢的那个。

也许查看字节码可以帮助理解发生了什么。如果你运行

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

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)版本的完整字节码在这里。

我(有限)的理解是,这两行指向A的初始化。请确认一下。

相关内容

  • 没有找到相关文章

最新更新