Почему в Mypy FAQ упоминается о влиянии на производительность? - PullRequest
0 голосов
/ 04 октября 2019

Насколько я понял, mypy - это инструмент, который проверяет код Python, содержащий аннотации типов.

Однако в FAQ я прочитал следующее:

Mypy выполняет только статическую проверку типов и не повышает производительность. Это оказывает минимальное влияние на производительность.

Во втором предложении «минимальный», по-видимому, подразумевает, что означает влияние на производительность (хотя и минимальное).

Почему mypy влияет на производительность? Я подумал, что, в конце концов, код все еще должен запускаться интерпретатором Python, поэтому mypy (или любой другой инструмент, который анализирует код, такой как flake8 или pylint) не должен оказывать никакого положительного или отрицательного влияния на производительность.

Это потому, что размер исходного кода больше из-за дополнительных аннотаций типов?

1 Ответ

3 голосов
/ 04 октября 2019

В разделе часто задаваемых вопросов говорится о производительности вашего кода 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 должен импортировать объект Listиз typing и прикрепите подсказки к созданному функциональному объекту.

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 - отложенная оценка аннотаций , нацеленнаяпри небольшом уменьшении этой стоимости и облегчении использования прямых ссылок. Для приведенного выше упрощенного примера это на самом деле не сокращает время, затрачиваемое на загрузку предопределенных аннотаций, также занимает некоторое время:

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

, но для более сложных, реальных проектов хинтинга типов это делаетсделать небольшую разницу.

...