Specialist


Specialist использует высокоточную информацию о местоположении (fine-grained location), чтобы наглядно показать пользователю, где и как новый адаптивный интерпретирующий транслятор (specializing adaptive interpreter) CPython 3.11 оптимизирует ваш код. Подробности об инструменте — к старту курса по Fullstack-разработке на Python.



Установка


Specialist поддерживает CPython 3.11+ на всех платформах.


Для установки просто выполните:


$ pip install specialist

Принцип работы


Исполняя ваш код, CPython 3.11 выявляет самые «нагруженные» (hot) фрагменты кода, которые исполняются так часто, что за их счёт можно оптимизировать время исполнения кода. Он «ускоряет» эти места, которые specialist размечает цветами.


Более тёмными и насыщенными цветами отмечается код, где «ускорить» можно много инструкций (то есть потенциал оптимизации велик). Места, где таких возможностей меньше, отмечены более светлыми и менее яркими цветами.


Как правило, «ускорение» происходит в три этапа:


  • Замена отдельных инструкций байт-кода на «адаптивные» формы. На самом деле они работают немного медленнее, чем обычные инструкции, поскольку периодически пытаются «оптимизироваться». Если они не могут сделать это, они остаются в адаптивной форме. specialist отмечает адаптивные инструкции красным цветом.


  • В некоторых случаях адаптивные инструкции преобразуются в гораздо более быстрые оптимизированные (specialized) инструкции. Примерами оптимизации являются доступ к атрибутам одного объекта или типа, вызовы некоторых «чисто пайтоновских» функций или сложение целых чисел. specialist использует зелёный цвет для обозначения оптимизированных инструкций.


  • Если через какое-то время оптимизация становится неэффективной (например, если выражение, которое ранее складывало два целых числа, вместо этого начинает объединять две строки), оптимизированную инструкцию можно преобразовать обратно в адаптивную. После этого цикл повторяется.



Specialist создан, чтобы дать представление о данном процессе как отладчикам самого CPython, так и пользователям, которые стремятся оптимизировать свой код.


Руководство


Пусть файл с исходным кодом conversions.py преобразует градусы по Фаренгейту в градусы по Цельсию и обратно:


import math

def f_to_c(f: float) -> float:
    """Convert Fahrenheit to Celsius."""
    x = f - 32
    return x * 5 / 9

def c_to_f(c: float) -> float:
    """Convert Celsius to Fahrenheit."""
    x = c * 9 / 5
    return x + 32

TEST_VALUES = [-459.67, -273.15, 0.0, 32.0, 42.0, 273.15, 100.0, 212.0, 373.15]

def test_conversions() -> None:
    for t in TEST_VALUES:
        assert_round_trip(t)

def assert_round_trip(t: float) -> None:
    # Round-trip Fahrenheit through Celsius:
    assert math.isclose(t, f_to_c(c_to_f(t))), f"{t} F -> C -> F failed!"
    # Round-trip Celsius through Fahrenheit:
    assert math.isclose(t, c_to_f(f_to_c(t))), f"{t} C -> F -> C failed!"

if __name__ == "__main__":
    test_conversions()

Запустить этот файл можно в CPython 3.11 из командной строки при помощи specialist:


$ specialist conversions.py

По завершении запуска скрипта specialist откроет браузер и отобразит размеченный исходный код программы:



Зелёным отмечены успешно оптимизированные фрагменты кода. Красным показаны фрагменты, которые оптимизировать не удалось (в виде «адаптивных» инструкций). Частично оптимизированные фрагменты показаны цветами из градиента «зелёный-жёлтый-оранжевый-красный, в зависимости от соотношения успехов и неудач оптимизации. Области кода, не затронутые попытками оптимизации, остались белыми.


Посмотрим на f_to_c и c_to_f. Здесь CPython не оптимизировал сложение с числом 32 и его вычитание. Сейчас он не поддерживает оптимизации бинарных операторов со смешанными значениями float и int. А эта часть кода посвящена именно этому.


Однако оптимизировать в нём сложение и вычитание двух значений float можно! Замена результата 32 на 32.0 сделает оптимизацию успешной (что подтверждено при повторном вызове specialist):



Подобное происходит и с умножением float на int. Можно продолжить преобразование постоянных значений во float:



Но есть способ лучше! Обратите внимание, что CPython вовсе не пытается оптимизировать деление (на иллюстрации оно осталось белым). Мы можем использовать оптимизацию CPython по складыванию констант, слегка изменив порядок операций, что позволит вычислить масштабные коэффициенты (5 / 9 и 9 / 5) на этапе компиляции. Когда мы это сделаем, CPython реализует наши конвертеры полностью с и помощью штатных операций с плавающей точкой:



Пара слов об оставшейся части кода:


  • Фрагмент, отвечающий за глобальный поиск TEST_VALUES, стал красным из-за того, что оптимизация подобных операций сейчас не поддерживается. Хотя раньше CPython мог оптимизировать test_conversions и определял их как нагруженный код, после просмотра TEST_VALUES (который происходит только один раз) этого не происходило. Неразумно тратить время на оптимизацию кода, который больше не будет выполняться!


  • По той же причине элементы assert в функции assert_round_trip выделены красным. Это «мёртвый» код: он не выполняется.


  • Вызов math.is_close отмечен оранжевым, ведь он реализован на C.
    Расширения C нельзя «встроить» в код аналогично вызовам функций «чистого Python, например c_to_f, f_to_c, assert_round_trip. По этой причине большая часть последовательности вызова того, что реализовано на C, не поддаётся оптимизации.



Режимы (modes)


Как и сам python, specialist можно использовать по-разному, например задавать путь к файлу:


$ specialist spam/eggs.py foo bar baz

название модуля:


$ specialist -m spam.eggs foo bar baz

или команду:


$ specialist -c 'import spam; spam.eggs()' foo bar baz

У него также есть опция поддержки выявления и анализа произвольных целевых файлов (target files) -t/--targets после исполнения скрипта. Это полезно, когда исполняемый скрипт отличается от кода, который хочется увидеть в specialist:


$ specialist --targets spam/eggs.py -c 'import uses_eggs; uses_eggs.run()'

Несколько файлов можно представить через glob:


$ specialist --targets 'spam/**/*.py' -m pytest

Specialist может записывать сгенерированные HTML-файлы в файловую систему, не открывая их в браузере — просто укажите путь к выходному каталогу в опции -o/-output:


$ specialist --output ../report --targets 'spam/**/*.py' -m pytest
/home/brandtbucher/sketch/spam/__init__.py -> /home/brandtbucher/report/__init__.html
/home/brandtbucher/sketch/spam/_spammy.py -> /home/brandtbucher/report/_spammy.html
/home/brandtbucher/sketch/spam/eggs/__init__.py -> /home/brandtbucher/report/eggs/__init__.html
/home/brandtbucher/sketch/spam/eggs/_eggy.py -> /home/brandtbucher/report/eggs/_eggy.html

Опции


-b/--blue


Оптимизированный код с этой опцией размечается синим, а не зелёным цветом. Некоторые люди лучше видят разницу цветов в градиенте «синий-фиолетовый-малиновый-красный», чем в стандартном градиенте «зелёный-жёлтый-оранжевый-красный».


-d/--dark


Код будет отображаться светлым текстом на тёмном фоне. Некоторым пользователям так удобнее.


А мы научим вас аккуратно работать не только с кодом, но и с данными^



Новогодняя акция — скидки до 50% по промокоду HABR:



Комментарии (7)


  1. Mihij
    09.12.2022 22:34

    В первый раз о таком слышу. Тема оптимизации кода сейчас весьма актуальна. Спасибо. В ближайшее время испробую.


  1. dyadyaSerezha
    10.12.2022 05:06

    например, если выражение, которое ранее складывало два целых числа, вместо этого начинает объединять две строки

    Вот тут не понял. Как это?


    1. Tzimie
      10.12.2022 11:17

      Это же Питон


      1. TyVik
        11.12.2022 08:36

        Простите, вы с js перепутали.


        1. Tzimie
          11.12.2022 15:27

          def sum(a, b): return a+b

          Будет суммировать и числа, и строки


      1. danSamara
        11.12.2022 17:07

        Мне кажется, вы хотели иронично пошутить, но вышло не очень.

        Во многих языках можно написать функцию, складывающую два типа/объекта, если для этого типа/объекта определенно сложение. В питоне и для строк и для чисел операция сложения определена.


        1. Tzimie
          11.12.2022 19:18

          Так я удивился вопросу поэтому