Предположим, вы разработали приложение или библиотеку на Python и уже готовитесь передать его / её  заказчику. И в этот момент возникают вопросы, о которых многие даже не задумываются.

Во-первых, так может оказаться, что вы разработали супер крутой алгоритм, которого ни у кого нет, и показывать его хочется только избранным.

Во-вторых, возникает вопрос окружения - хочется быть уверенным, что заказчик справится с установкой правильной версии Python и всех вспомогательных библиотек, но это не всегда простая задача. Было бы удобно упаковать приложение в автономный исполняемый файл.

И, наконец, хочется,  чтобы конечное приложение работало быстрее, чем в среде разработки.

И вот тут настало время скомпилировать Python-код. Меня зовут Руслан, я старший разработчик компании «Цифровое проектирование». Сегодня я расскажу, как выбрать тот самый компилятор из множества доступных.

AOT/JIT

Компиляция – это сборка программы, включающая: трансляцию всех модулей программы, написанных на языке программирования высокого уровня, в эквивалентные программные модули на низкоуровневом языке, близком  к машинному коду, или на машинном языке и сборку исполняемой программы. Существует два вида компиляции:

  • AOT-компиляция (ahead-of-time) – компиляция перед исполнением программы. Т.е. программа компилируется один раз, в результате компиляции получается исполняемый файл.

  • JIT-компиляция (just-in-time) – компиляция во время исполнения программы. Т.е. программа (а точнее, блоки программы) компилируется много раз - при каждом запуске. 

Бенчмарк

Так как одной из целей является ускорение, необходимо оценить, насколько быстро работает скомпилированный код. В качестве бенчмарка будем использовать pyperfomance. К сожалению, pyperfomance не подошел для Cython и Pythran, потому что не позволяет визуализировать все возможности языка. Ускорения для Cython без модификации кода получить не удалось, а Pythran не умеет в пользовательские классы. Для них воспользуемся вычислением числа пи:

def approximate_pi(n):
   step = 1.0 / n
   result = 0
   for i in range(n):
   		x = (i + 0.5) * step
   		result += 4.0 / (1.0 + x * x)
   return step * result

Эксперименты будем проводить на процессоре Intel Core i7 10510U. На CPython 3.9.7 время вычисления числа пи до 100.000.000 знака заняло 5.82 секунды.

AOT-компиляция Python

PyInstaller

PyInstaller упаковывает приложения Python в автономные исполняемые файлы в Windows, GNU / Linux, Mac OS X, FreeBSD, Solaris и AIX. 

Устанавливается через pip:

pip install pyinstaller

После установки для создания исполняемого файла достаточно выполнить команду:

pyinstaller <имя_файла>.py

В результате будет создано:

  • *.spec – файл спецификации (используется для ускорения будущих сборок приложения, связи файлов данных с приложением, для включения .dll и .so файлов, добавление в исполняемый файл параметров runtime-а Python);

  • build/ – директория с метаданными для сборки исполняемого файла;

  • dist/ – директория, содержащая все зависимости и исполняемый файл.

Сборку приложения можно настроить с помощью параметров командной строки:

  • --name – изменение имени исполняемого файла (по умолчанию, такое же, как у сценария);

  • --onefile – создание только исполняемого файла (по умолчанию, папка с зависимостями и исполняемый файл);

  • --hidden-import – перечисление импортов, которые PyInstaller не смог обнаружить автоматически;

  • --add-data – добавление в сборку файлов данных;

  • --add-binary – добавление в сборку бинарных файлов;

  • --exclude-module – исключение модулей из исполняемого файла;

  • --key – ключ шифрования AES256. Да, приложение не будет содержать исходного кода, но его можно декомпилировать, например, так: Pyinstaller Extractor (.exe → .pyc) и uncompile6 (.pyc → .py).  Для скрытия исходного кода можно обфусцировать байт-код Python с помощью шифрования.

У PyInstaller есть ограничения. Он работает с Python 3.5–3.9. Поддерживает создание исполняемых файлов для разных операционных систем, но не умеет выполнять кросскомпиляцию, т. е. необходимо генерировать исполняемый файл для каждой ОС отдельно. Более того, исполняемый файл зависит от пользовательского glibc, т. е. необходимо генерировать исполняемый файл для самой старой версии каждой ОС.

PyInstaller знает о многих Python-пакетах и умеет их учитывать при сборке исполняемого файла. Но не о всех. Например, фреймворк uvicorn практически весь нужно явно импортировать в файл, к которому будет применена команда pyinstaller. Полный список поддерживаемых из коробки пакетов можно посмотреть здесь.

Cython

Cython - это оптимизирующий статический компилятор как для языка программирования Python, так и для расширенного языка программирования Cython. С его помощью можно код на Python транслировать в С и затем скомпилировать в бинарник, совместимый с CPython. Компиляцию придется делать под все операционные системы и архитектуры процессора.

Ставится Cython через pip:

pip install Cython

Рассмотрим его работу на примере с вычислением числа пи:

  1. Немного модифицируем нашу функцию:

    def approximate_pi(int n):
       cdef float step
       cdef float result
       cdef float x
       step = 1.0 / n
       result = 0.0
       for i in range(n):
       		x = (i + 0.5) * step
       		result += 4.0 / (1.0 + x * x)
    
       return step * result
    
  2. Cython → C:

    cython -2  pi_approximater.pyx -o pi_approximater.c

  3. Компилируем С-шный код:

    gcc -g -O2 -shared -o pi_approximater.so pi_approximater.c python-config --includes --ldflags -fPIC

  4. И замеряем время на бенчмарке: 3,66 секунды.

А что делать, если в нашем проекте несколько файлов, которые нужно скомпилировать? Тогда нужно использовать так называемый сценарий сборки. С его помощью можно модернизировать сборку в зависимости от операционной системы, указывать несколько файлов, которые необходимо скомпилировать, и многое другое.

Создадим файл build.py:

from distutils.core import setup
from Cython.Build import cythonize

setup(
   ext_modules=cythonize("bench_cython.pyx"),
)

Запустим: python build.py build_ext –-inplace

В результате будет сгенерирован .so/.dll файл.

Nuitka

Nuitka способна упаковывать приложения Python в автономные исполняемые файлы, а также транслировать Python-код в С для его последующей компиляции. Работает с разными версиями Python (2.6, 2.7, 3.3 - 3.9).

Ставится через pip:

pip install nuitka

Для генерации исполняемого файла достаточно выполнить команду:

python -m nuitka --follow-import some_program.py

Для компиляции модуля:

python -m nuitka --module some_module.py

Для компиляции пакета:

python -m nuitka --module some_package --include-package = some_package

Pythran

Pythran – статический компилятор Python, позиционирующий себя как ориентированный на научные вычисления и использующий преимущества многоядерных процессоров и блоков инструкций SIMD. Он транслирует Python-код, аннотированный описаниями интерфейса, в C++. До версии 0.9.5 (включительно) Pythran поддерживал Python 3 и Python 2.7. Последние версии поддерживают только Python 3.

Установка:

pip install pythran

Генерируем бинарный файл <имя файла>.so:

pythran <имя файла>.py

Pythran по умолчанию не умеет в пользовательские классы, поэтому при попытке их компиляции будет выброшена ошибка:

Top level statements can only be assignments, strings,functions, comments, or imports

Добавим комментарий аннотации функции:

#pythran export approximate_pi(int)  
def approximate_pi(n):
   step = 1.0 / n
   result = 0
   for i in range(n):
   		x = (i + 0.5) * step
   		result += 4.0 / (1.0 + x * x)
   return step * result

Скомпилируем и бенчмарк выдает 0,00007 секунды.

cx-Freeze

cx-Freeze – это набор скриптов и модулей преобразования скриптов Python в исполняемые файлы. cx_Freeze - кроссплатформенный. Он поддерживает Python 3.5.2 и выше.

Ставится с помощью pip:

pip install cx_Freeze

Для генерации исполняемого файла достаточно выполнить команду:

cxfreeze <имя файла>.py

Сборку можно настроить с помощью параметров командной строки:

  • -h, --help - справка;

  • -O - оптимизировать сгенерированный байт-код согласно PYTHONOPTIMIZE;

  • -c, --compress - сжать байт-код в zip-файлах;

  • -s, --silent - выводить только предупреждения и ошибки;

  • --target-dir=DIR, --install-dir=DIR - каталог, в который следует поместить целевой файл и все зависимые файлы.

Также возможно использование сценария сборки, например, так:

import sys
from cx_Freeze import setup, Executable
 
# Dependencies are automatically detected, but it might need fine tuning.
build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]}
 
# GUI applications require a different base on Windows (the default is for a
# console application).
base = None
if sys.platform == "win32":
    base = "Win32GUI"
 
setup(  name = "guifoo",
        version = "0.1",
        description = "My GUI application!",
        options = {"build_exe": build_exe_options},
        executables = [Executable("guifoo.py", base=base)])

Сборка исполняемого файла:

 python setup.py build

JIT-компиляция Python

JIT-компиляция не позволяет скрывать исходники или создавать автономный исполняемый файл, но дает возможность значительно ускорить выполнение программы.

PyPy

PyPy - интерпретатор языка программирования Python 2.7 и Python 3.7. Он написан на RPython и содержит:

  • компилятор байт-кода, отвечающий за создание объектов кода Python из исходного кода пользовательского приложения;

  • оценщик байт-кода, ответственный за интерпретацию объектов кода Python;

  • стандартное объектное пространство, отвечающее за создание и управление объектами Python, видимыми приложением.

PyPy поддерживает сотни библиотек Python, включая NumPy.

Основные особенности (сравнение с CPython):

  • Скорость. При выполнении длительно выполняющихся программ, когда значительная часть времени тратится на выполнение кода Python, PyPy может значительно ускорить ваш код.

  • Использование памяти. Программы Python, требующие много памяти (несколько сотен Мб или более), могут занимать меньше места, чем в CPython. Однако это не всегда так, поскольку зависит от множества деталей. Также базовый уровень потребления оперативной памяти выше, чем у CPython.

Скачать PyPy можно с здесь. После скачивания PyPy готов к запуску после распаковки архива. Если необходимо сделать PyPy доступным для всей системы, достаточно поместить символическую ссылку на исполняемый файл pypy в /usr/local/bin. Также можно поставить с помощью pyenv.

PyPy работает на Mac, Linux (не все дистрибутивы) или Windows.

Для запуска кода с помощью PyPy вместо команды python3 (как c помощью CPython) достаточно воспользоваться командой pypy3:

 pypy3 something.py

Pyston

Pyston - это форк CPython 3.8.8 с дополнительной оптимизацией производительности. В настоящее время он поддерживает установку только из исходников. Или с помощью pyenv.

В Pyston поддерживаются все возможности CPython, в том числе C API для разработки расширений на языке Си. Среди основных отличий Pyston от CPython помимо общих оптимизаций выделяется использование DynASM JIT и inline-кэширования

Заключение

Итак, мы рассмотрели 5 фреймворков AOT-компиляции Python. Для любителей аналитики, ниже приведена таблица со сравнительным анализом.

PyInstaller

Cython

Nuitka

Pythran

cx-Freeze

Генерация автономных исполняемых файлов

+

-

+

-

+

Компиляция python-модуля в исполняемый файл, совместимый с CPython

-

+

+

+

+

Компиляция байт-кода Python

+

-

+

-

+

Трансляция Python в С/C++

-

+

+

+

-

Кроссплатформенность

+

+

+

+

+

Кросскомпиляция

-

-

-

-

-

Среднее относительное время выполнения кода на различных версиях CPython, интерпретаторах с поддержкой JIT-компиляции и после сборки на фреймворках AOT-компиляции:

Список литературы

  1. http://www.pyinstaller.org/

  2. https://cython.org/

  3. https://nuitka.net/

  4. https://github.com/serge-sans-paille/pythran

  5. https://cx-freeze.readthedocs.io/en/latest/

  6. https://github.com/pyston/pyston

  7. https://www.pypy.org/

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


  1. Mistx
    06.10.2021 16:39
    -7

    М-да, чего только люди не придумают, чтобы не писать на C/C++...


    1. Neferot
      06.10.2021 17:01
      +12

      -М-да, чего только люди не придумают, что бы не писать на Assembler.....

      -М-да. чего только люди не придумают, что бы не писать на машинных кодах....


      1. Borjomy
        08.10.2021 09:13
        -4

        Т.е вы считаете нормальным, когда программист очень удобно для себя лабает программу, которая работает с такой скоростью, что заказчику быстрее посчитать на калькуляторе? При этом он хвастается, какой он крутой и как круто программировать на самом популярном языке в мире?


        1. Neferot
          09.10.2021 15:24
          +1

          Т.е вы считаете что кто не программирует на ассамблере тот не может считать себя крутым программистом?
          Имхо, язык программирования всего лишь инструмент, для каждого задания\проблемы желательно использовать наиболее подходящий для этого инструмент, с другой стороны если мне нужно разбить орех - не вижу ничего зазорного разбить его камнем, или другим подручным предметом а не идти искать часик-другой - где же завалялся у меня орехокол.
          Так же считаю нормальным что вместе с функциональностью чего-то важно и удобство\комфорт использования чего-то. Нас окружает куча вещей которые уменьшают свою функциональность во имя комфорта и легкости использования. И я не вижу в этом ничего плохого.
          В ту же очередь вот вы называете пайтон самым популярным языком, и именно ИМХО эта популярность и пришла из-за его комфорта и легкости освоения. Теперь ученый, или специалист другой профессии может автоматизировать себе часть работы, + даже обработать данные не прибегая к помощи проф. программистов со стороны. И это на мое скромное мнение замечательно.
          По поводу именно компилятора пайтон - весь цимес не в том что бы сделать его быстрее, выше, сильнее - а о переносе его в другое место. Вот например некая небольшая утилитка которую вы написали для своего удобства, и вам по какой-то причине хочется\нужно поделится с человеком который далек от этого всего но ему нужен в даный момент функционал этой утилитки. Вместо инструкции по установке - пайтон, потом пип, потом инсталирования нужных пакетов, можно просто завернуть это все в исполняемый файл и отправить ему. Разве это не круто? И почему-то часть программистов думает что это вот все отбирает их хлеб, что специалист не программист не имеет права ничего кодить для себя или для своей работы, такая своеобразная кастовая структура. И мне кажется что это не правильно.


        1. DaneSoul
          14.10.2021 15:35

          Т.е вы считаете нормальным, когда программист очень удобно для себя лабает программу, которая работает с такой скоростью, что заказчику быстрее посчитать на калькуляторе
          Как же в таком случае по Вашему используется Python в data science и machine learning (а он там, к сведению, один из основных инструментов!) которые на данный момент, пожалуй, самые ресурсоемкие сферы программирования?
          А все очень просто — низкоуровневые библиотеки пишут на C++, а вот всю обвязку при их использовани на Python — получается и быстро и удобно.


      1. Mistx
        13.10.2021 18:15

        Да вы прям мастер аппроксимации

        https://sun9-40.userapi.com/impf/Raf2loXoer78Loc-BSLRKp2X3ZdJJ7oI91lzTA/scmVm-4BQi4.jpg?size=1590x400&quality=95&crop=81,29,1222,307&sign=e3e3638e87cc4d3136b9fe6372710788&type=cover_group

        А если серьёзно, то для каждой задачи и условий есть соответствующие инструменты. Писать сайты на 1С или ресурсо-эффективные алгоритмы на Python - бред. Натягивать сову на глобус - любимое занятие новичков во всех сферах человеческой деятельности...

        И да, я не вижу ничего зазорного в том чтобы писать определённые участки кода на asm или даже в машинных кодах (загрузчики, непортабельные модули ядра и т.п.)

        Python - отличный инструмент для прототипирования, user space и стартапов, но ему не место в нагруженных проектах на проде, ИМХО.


        1. Neferot
          13.10.2021 20:05

          А вы читали мой следующий комментарий? \https://habr.com/ru/company/numdes/blog/581374/comments/#comment_23572348\ Позволю себе наглость процитировать самого-себя - "Имхо, язык программирования всего лишь инструмент, для каждого задания\проблемы желательно использовать наиболее подходящий для этого инструмент". Зачем вы мне пересказываете своими словами мой пост?
          Так же из вашего ответа я так и не понял - почему таки компилятор Пайтона не должен существовать, ведь ,как я понял, именно так считает автор первоначального комментария. Как я выше и говорил на каждые потребности желательно использовать свой инструмент - с другой стороны есть неплохие вещи написанные на\по крайней мере часть проекта, на Пайтоне. \Если верить интернетам\
          Например - Mount and blade - Все скрипты написаны на Python. Игра отлично работает на слабых машинах.
          EVE Online Python использовался для создания игровой логики и управления серверной частью игры. Разработчики использовали улучшенную версию интерпретатора, которая называется stackless python. Так как это ММО, сервер может обрабатывать миллионы запросов, и stackless python отлично справляется с этим.
          Civilization 4 - Разработчики не ограничились использованием Python для реализации каких-то частей проекта, они написали на нём практически всю игру.

          Blender - Python используется для создания логики, импорта и экспорта, автоматического выполнения задач и работы инструментов.

          AlphaStar – искусственный интеллект для Starcraft 2 написан на Пайтон.

          Поисковик Гугл, Ютуб, Инстаграм, Фейсбук, Dropbox, Spotify, Netflix, Quora, Uber - так же содержат не маленькие части на Пайтоне - разве это не нагруженные проекты. Почему не писали эти части на более "быстрых" языках? Андроид вот тоже джава машину тащит а не использует нечто быстрее. Значит были наверное у них причины юзать Пайтон итд. Как я и говорил ранее кроме быстродействия так же важно и удобство, функциональность и еще куча всяких нюансов.
          Еще раз повторюсь - не все проф программисты, и не все проф программисты пишут исключительно высоконагружаемые системы, статистикой не владею, но мне кажется совсем наоборот только небольшое количество прогеров пишут высоконагружаемые системы.


          1. Mistx
            14.10.2021 15:18

            Комментарий не сразу увидел - только после полного обновления страницы.

            Я ничего против Python не имею и активно использую в своих проектах. Просто на мой взгляд - если уж пришла пора оптимизировать скрипт, то лучше заново написать на стандартном компилируемом низкоуровневом языке, а не пытаться переписывать скрипт, чтобы соответствовать специфическим требованиям маловостребованного редкоиспользуемого компилятора, попутно изучая его потроха.


  1. pexey40317
    06.10.2021 17:22
    +1

    Все хорошо, но текст на графиках бы покрупнее.


    1. NumDes Автор
      06.10.2021 17:24

      Графики должны быть кликабельны.


  1. Daiwery
    06.10.2021 17:49
    +1

    Даже не верится, что у Pyhtran ускорение по сравнению с обычным Python произошло в ~80 тысяч раз.


  1. PythonAnalyst
    06.10.2021 20:52

    Год назад Pypy (да и думаю сейчас также) вываливался с ошибкой, если встречал модуль, написанный на С (в том числе numpy, несмотря на все обещания, что так не будет). Так что использовать его можно в ограниченном количестве проектов (например, все библиотеки ML написаны на С/С++).


    1. ruslanmurzin98
      07.10.2021 11:21
      +1

      Спасибо за замечание. Действительно у PyPy есть проблемы с некоторыми проектами, написанными на С/C++ (например, pytorch). Но numpy работает, если взять колесо, собранное под PyPy. Буду благодарен за примеры случаев, где не завелось.


  1. zueve
    06.10.2021 22:03
    +4

    На сколько я понимаю PyInstaller не является компилятором. Это просто упаковщик который запихивает интерпритатор и ваши пакеты в один файл, их легко можно потом достать, а ускорение достигается за счёт быстрого импорта, т.к. все данные уже в памяти. Если интересно подробнее разобраться в компиляции python то вот статья Как скомпилировать python


  1. PythonAnalyst
    07.10.2021 07:54
    +2

    Когда начинал писать на Python, то тоже парился по поводу производительности. Но со временем понял, что надо научиться в коде отличать "обвязку" от высоконагруженных частей. Python надо использовать только как "клей", который соединяет высоконагруженные части, написанные на С/С++. Например, тяжелые алгоритмы ML написаны на С/С++, а весь остальной вызывающий код занимает сотые доли процента времени выпонения (и поэтому неважно насколько эффективно он выпоняется). Это очень похоже на работу с базой данных: "тяжелые" обращения к серверу будут определять скорость работы, а не клиентский код, формирующий эти запросы / отображающий результаты.


  1. Hivemaster
    07.10.2021 09:44

    Предположим, вы разработали приложение или библиотеку на Python и уже готовитесь передать его / её  заказчику. И в этот момент возникают вопросы, о которых многие даже не задумываются.

    Например, зачем я выбрал интерпретируемый язык для программы, которую нужно компилировать?


    1. ruslanmurzin98
      07.10.2021 11:20
      +1

      На старте проекта TTM важнее производительности. С помощью описанных в статье компиляторов можем выжать максимум из Python перед переписыванием на C++.


  1. erdbeeranke
    07.10.2021 22:29
    -1

    использовать одну простую числодробилку, которая почти не используюет каких-то особенностей языка, для бенчмарков предельно глупая затея. но ещё глупее считать, что приведённый высер апроксимирует число пи.


  1. constructor
    20.10.2021 12:23

    я тоже как то пытался искать хороший инструмент для упаковки (или компиляции) кода Python.
    как только начинаешь запускать инструмент на чем то настоящем (включая UI) начинаются сыпаться многочисленные проблемы.

    с большим трудом pyinstaller (определенной версии, определенные версии пакетов) создает exe файл, который можно таки запустить.
    но и даже это не всегда работает (например, а одном месте антивирус заблокировал запуск такого exe файла - тогда опять надо искать версию pyinstaller или пытаться апдейтить антивирус).
    мой опыт говорит, что нет хорошо работающего решения.
    но хотя бы спасибо pyinstaller, он наиболее близок в моих случаях.


  1. kx13
    13.11.2021 21:21

    Почему-то сравниваются вещи сделанные принципиально для разных задач.

    PyInstaller и cx-Freeze просто упаковщики питоновского кода со всеми библиотеками в отдельный архив и приделывание к нему exe файла для быстрого запуска. На производительность кода никак не влияют

    Cython и Nuitka уже переводять код на питоне в код на языке C.

    Причем для Cython надо самому код писать, который по сути Си с синтаксисом Python. Nutika пытается кое-что делает по проще. После этих инструментов программы могут действительно работать гораздо быстрее. Они вообще не про распространение пакетов.

    А если нужно распостранять питоновские проекты, то все же лучше делать все через setuptools. Создать setup.py сделать архив с помощью команды sdist и отдать закачику.

    Проблем гораздо меньше, чем со всякими PyInstaller.

    Правда ему придется самому еще Python установить, но он ставится нажатием на кнопочку Next 5 раз (или около того) :)