为什么 mypy 常见问题解答提到性能影响?
Why is the mypy FAQ mentioning performance impact?
据我了解,mypy
是一个工具,可以检查 python 包含类型注释的代码。
然而,在 FAQ 中,我阅读了以下内容:
Mypy only does static type checking and it does not improve performance. It has a minimal performance impact.
在第二句话中,"minimal" 似乎暗示 对性能有影响(尽管很小)。
为什么 mypy 会影响性能?我认为最后,代码仍然必须由 python 解释器 运行,所以 mypy(或任何其他分析代码的工具,如 flake8 或 pylint)应该不会有任何影响,积极或消极,对性能。
是否因为额外的类型注解导致源代码体积变大?
FAQ 讨论了您的 Python 代码的性能。
在某些编程语言中,类型提示可以帮助引导即时编译器更有效地编译提示代码,从而提高性能。在 Python 中,情况并非如此,语言 运行time 不使用类型提示,类型提示仅被视为元数据。
最小的性能影响来自 运行 提示定义(导入、TypeVar
分配和解释注释本身)所需的额外字节码。这种影响确实很小,即使在重复创建 类 和函数时也是如此。
您可以通过在代码 运行 via exec()
中使用类型提示使影响可见;这是一个极端的例子,我们在代码中增加了很多开销,但代码做的很少:
>>> import timeit
>>> without_hints = compile("""def foo(bar): pass""", "", "exec")
>>> with_hints = compile(
... "from typing import List\ndef foo(bar: List[int]) -> None: pass",
... "", "exec")
>>> without_metrics = timeit.Timer('exec(s)', 'from __main__ import without_hints as s').autorange()
>>> with_metrics = timeit.Timer('exec(s)', 'from __main__ import with_hints as s').autorange()
>>> without_metrics[1] / without_metrics[0] * (10e6)
4.217094169580378
>>> with_metrics[1] / with_metrics[0] * (10e6) # microseconds per execution
19.113581199781038
因此添加类型提示增加了大约 15 微秒的执行时间,因为 Python 必须从 typing
导入 List
对象,并将提示附加到创建的函数对象。
15 微秒对于在模块顶层定义的任何东西来说是最小的,只需要导入一次。
反汇编生成的字节码就可以看到这一点。比较没有提示的版本:
>>> dis.dis(without_hints)
1 0 LOAD_CONST 0 (<code object foo at 0x10ace99d0, file "<dis>", line 1>)
2 LOAD_CONST 1 ('foo')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (foo)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE
Disassembly of <code object foo at 0x10ace99d0, file "<dis>", line 1>:
1 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
使用提示的版本:
>>> import dis
>>> dis.dis(with_hints)
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('List',))
4 IMPORT_NAME 0 (typing)
6 IMPORT_FROM 1 (List)
8 STORE_NAME 1 (List)
10 POP_TOP
2 12 LOAD_NAME 1 (List)
14 LOAD_NAME 2 (int)
16 BINARY_SUBSCR
18 LOAD_CONST 2 (None)
20 LOAD_CONST 3 (('bar', 'return'))
22 BUILD_CONST_KEY_MAP 2
24 LOAD_CONST 4 (<code object foo at 0x10ace99d0, file "<dis>", line 2>)
26 LOAD_CONST 5 ('foo')
28 MAKE_FUNCTION 4 (annotations)
30 STORE_NAME 3 (foo)
32 LOAD_CONST 2 (None)
34 RETURN_VALUE
Disassembly of <code object foo at 0x10ace99d0, file "<dis>", line 2>:
2 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
Python 3.7引入了PEP 563 -- Postponed Evaluation of Annotations,目的是稍微降低这个开销,让前向引用更容易。对于上面过度简化的示例,这实际上并没有减少加载预定义注释所花费的时间:
>>> pep563 = compile(
... "from __future__ import annotations\nfrom typing import List\ndef foo(bar: List[int]) -> None: pass",
... "", "exec")
>>> pep563_metrics = timeit.Timer('exec(s)', 'from __main__ import pep563 as s').autorange()
>>> pep563_metrics[1] / pep563_metrics[0] * (10e6) # microseconds per execution
19.314851402305067
但对于更复杂的、现实生活中的类型提示项目,这确实有一点不同。
据我了解,mypy
是一个工具,可以检查 python 包含类型注释的代码。
然而,在 FAQ 中,我阅读了以下内容:
Mypy only does static type checking and it does not improve performance. It has a minimal performance impact.
在第二句话中,"minimal" 似乎暗示 对性能有影响(尽管很小)。
为什么 mypy 会影响性能?我认为最后,代码仍然必须由 python 解释器 运行,所以 mypy(或任何其他分析代码的工具,如 flake8 或 pylint)应该不会有任何影响,积极或消极,对性能。
是否因为额外的类型注解导致源代码体积变大?
FAQ 讨论了您的 Python 代码的性能。
在某些编程语言中,类型提示可以帮助引导即时编译器更有效地编译提示代码,从而提高性能。在 Python 中,情况并非如此,语言 运行time 不使用类型提示,类型提示仅被视为元数据。
最小的性能影响来自 运行 提示定义(导入、TypeVar
分配和解释注释本身)所需的额外字节码。这种影响确实很小,即使在重复创建 类 和函数时也是如此。
您可以通过在代码 运行 via exec()
中使用类型提示使影响可见;这是一个极端的例子,我们在代码中增加了很多开销,但代码做的很少:
>>> import timeit
>>> without_hints = compile("""def foo(bar): pass""", "", "exec")
>>> with_hints = compile(
... "from typing import List\ndef foo(bar: List[int]) -> None: pass",
... "", "exec")
>>> without_metrics = timeit.Timer('exec(s)', 'from __main__ import without_hints as s').autorange()
>>> with_metrics = timeit.Timer('exec(s)', 'from __main__ import with_hints as s').autorange()
>>> without_metrics[1] / without_metrics[0] * (10e6)
4.217094169580378
>>> with_metrics[1] / with_metrics[0] * (10e6) # microseconds per execution
19.113581199781038
因此添加类型提示增加了大约 15 微秒的执行时间,因为 Python 必须从 typing
导入 List
对象,并将提示附加到创建的函数对象。
15 微秒对于在模块顶层定义的任何东西来说是最小的,只需要导入一次。
反汇编生成的字节码就可以看到这一点。比较没有提示的版本:
>>> dis.dis(without_hints)
1 0 LOAD_CONST 0 (<code object foo at 0x10ace99d0, file "<dis>", line 1>)
2 LOAD_CONST 1 ('foo')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (foo)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE
Disassembly of <code object foo at 0x10ace99d0, file "<dis>", line 1>:
1 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
使用提示的版本:
>>> import dis
>>> dis.dis(with_hints)
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('List',))
4 IMPORT_NAME 0 (typing)
6 IMPORT_FROM 1 (List)
8 STORE_NAME 1 (List)
10 POP_TOP
2 12 LOAD_NAME 1 (List)
14 LOAD_NAME 2 (int)
16 BINARY_SUBSCR
18 LOAD_CONST 2 (None)
20 LOAD_CONST 3 (('bar', 'return'))
22 BUILD_CONST_KEY_MAP 2
24 LOAD_CONST 4 (<code object foo at 0x10ace99d0, file "<dis>", line 2>)
26 LOAD_CONST 5 ('foo')
28 MAKE_FUNCTION 4 (annotations)
30 STORE_NAME 3 (foo)
32 LOAD_CONST 2 (None)
34 RETURN_VALUE
Disassembly of <code object foo at 0x10ace99d0, file "<dis>", line 2>:
2 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
Python 3.7引入了PEP 563 -- Postponed Evaluation of Annotations,目的是稍微降低这个开销,让前向引用更容易。对于上面过度简化的示例,这实际上并没有减少加载预定义注释所花费的时间:
>>> pep563 = compile(
... "from __future__ import annotations\nfrom typing import List\ndef foo(bar: List[int]) -> None: pass",
... "", "exec")
>>> pep563_metrics = timeit.Timer('exec(s)', 'from __main__ import pep563 as s').autorange()
>>> pep563_metrics[1] / pep563_metrics[0] * (10e6) # microseconds per execution
19.314851402305067
但对于更复杂的、现实生活中的类型提示项目,这确实有一点不同。