繁体   English   中英

jupyterLab %%timeit 与 python timeit 不一致

[英]jupyterLab %%timeit not consistent with python timeit

我想测试类型提示是否会影响代码运行的时间。 它可能会增加一点时间,因为编译器必须忽略它,这需要时间,但我想看看这是多么微不足道。

为此,我在 jupyterLab 上执行了此操作: 在此处输入图像描述

但是,我的一位同学在没有 jupyterLab 的情况下尝试了这个,发现: 在此处输入图像描述

有人对为什么会发生这种情况有解释吗?

使用的功能:

def func(a, b):
    return a + b
func(6, 7)    

和:

def func(a: int, b: int) -> int:
    return a + b
func(6, 7)

正如您自己注意到的那样, “函数定义需要更多时间”

为什么你看到了不同

Pythengers,拆开!

import dis

dis.dis('''
def func(a, b):
    return a + b
''')

输出(使用 Python 3.10.4):

  2           0 LOAD_CONST               0 (<code object func at 0x00000243938989D0, file "<dis>", line 2>)
              2 LOAD_CONST               1 ('func')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (func)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

Disassembly of <code object func at 0x00000243938989D0, file "<dis>", line 2>:
  3           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

加载已编译的代码对象,从中创建一个函数对象,并将其分配给名称func

使用带注释的版本,我们得到的是:

  2           0 LOAD_CONST               0 ('a')
              2 LOAD_NAME                0 (int)
              4 LOAD_CONST               1 ('b')
              6 LOAD_NAME                0 (int)
              8 LOAD_CONST               2 ('return')
             10 LOAD_NAME                0 (int)
             12 BUILD_TUPLE              6
             14 LOAD_CONST               3 (<code object func at 0x00000243940F0A80, file "<dis>", line 2>)
             16 LOAD_CONST               4 ('func')
             18 MAKE_FUNCTION            4 (annotations)
             20 STORE_NAME               1 (func)
             22 LOAD_CONST               5 (None)
             24 RETURN_VALUE

Disassembly of <code object func at 0x00000243940F0A80, file "<dis>", line 2>:
  3           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

因此,当被执行时,会为注释完成一些额外的工作,这会花费额外的时间。

为什么你的同学没有看到不同

为什么这对你同学的测量方式没有影响? 因为他们并没有真正衡量它。 尝试在a.py中打印一些东西:

print('importing a.py ...')
def func(a, b):
    return a + b
func(6, 7)

然后再试一次:

> python -m timeit -n 10000000 "import a"
importing a.py ...
10000000 loops, best of 5: 370 nsec per loop
>

一千万次循环,但我们的消息只打印了一次 因为 Python 缓存导入并且不会重新导入已经导入的内容。 因此,虽然这确实衡量了函数定义的单次执行,但在执行一千万个 import 语句中这完全是微不足道的。 真的,你的同学没有测量他们打算测量的代码,而是测量了导入语句(几乎所有这些都被识别为重新导入然后被忽略)。

我认为诸如%%timeit之类的魔术函数会导致笔记本解释单元格。 在命令行和笔记本中运行你的代码,我得到了与你相似的结果。

但是,如果我在一个单元格中定义函数afuncbfunc ,然后在不同的单元格中分别定义%%timeit ,则结果再次彼此一致。

[1]
def afunc(a: int, b: int) -> int:
    return a + b

def bfunc(a, b):
    return a + b
[2]
%%timeit
afunc(1,2)
40.9 ns ± 0.131 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
[3]
%%timeit
bfunc(1,2)
42.9 ns ± 0.0417 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

没有显着差异,而

[1]
%%timeit
def afunc(a: int, b: int) -> int:
    return a + b
afunc(1,2)

145 ns ± 0.765 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
[2]
%%timeit
def bfunc(a, b):
    return a + b
bfunc(1,2)
71.2 ns ± 0.341 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

给我们带来了巨大的不同

我们可以更进一步,这样做:

[1]
%%timeit
def afunc(a: int, b: int) -> int:
    return a + b
111 ns ± 0.358 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
[2]
%%timeit
def bfunc(a, b):
    return a + b
32.6 ns ± 0.255 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

这里很明显,解释是测量的,因为没有执行用户函数调用,只是函数本身的定义。 我们观察到相同的差异,减去运行任一功能所需的约 40ns。

控制台中更简单的代码似乎证实了这不是 JupyterLab 特有的,尽管这段代码更容易满足exec的要求:

C:\Users\Ben>python -m timeit "exec('a: int = 1')"
50000 loops, best of 5: 4.91 usec per loop

C:\Users\Ben>python -m timeit "exec('a = 1')"
50000 loops, best of 5: 4.02 usec per loop

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM