【问题标题】:Why is the mypy FAQ mentioning performance impact?为什么 mypy 常见问题解答提到性能影响?
【发布时间】:2019-10-04 14:17:37
【问题描述】:

据我了解,mypy 是一个检查包含类型注释的 Python 代码的工具。

但是,在FAQ 中,我读到了以下内容:

Mypy 只做静态类型检查,并不能提高性能。它对性能的影响很小。

在第二句中,“最小”似乎暗示存在对性能的影响(尽管很小)。

为什么 mypy 会影响性能?我认为最后,代码仍然必须由 python 解释器运行,因此 mypy(或任何其他分析代码的工具,如 flake8 或 pylint)不应该对性能产生任何正面或负面的影响。

是不是因为额外的类型注解导致源代码体积变大?

【问题讨论】:

  • 不是mypy本身;它是用于创建提示的表达式。请注意,在 Python 4 中,根本不会评估注释,而只是将其保存为字符串。 (不,Python 4 的开发没有时间表;相反,任何仍评估注释的版本仍将是 3.x 版本。)

标签: python mypy


【解决方案1】:

常见问题解答讨论了 Python 代码的性能。

在某些编程语言中,类型提示可以帮助引导即时编译器更有效地编译提示代码,从而提高性能。在 Python 中情况并非如此,语言运行时不使用类型提示,类型提示仅被视为元数据。

最小的性能影响来自运行提示定义(导入、TypeVar 分配和解释注释本身)所需的额外字节码。即使重复创建类和函数,这种影响也确实很小。

您可以通过在通过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

但对于更复杂的、现实生活中的类型提示项目,这确实会产生很小的影响。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 2013-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多