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

Кого не удивляло узкое место в производительности в проекте Python? Когда мы погружаемся в расширенные варианты использования с проблемами производительности, мы часто достигаем жесткого предела с чистым кодом Python. Как мы можем оптимизировать код Python, если экосистемы с батарейками недостаточно?

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

Встроенная оптимизация принимает форму компилятора JIT (генерация машинного кода точно в срок) или компилятора AOT (генерация машинного кода с опережением времени). Решения JIT будут интегрироваться напрямую с вашим кодом Python. Решения AOT должны скомпилировать двоичный объект в собственной общей библиотеке, импортированной в среду выполнения Python. С этими решениями прирост производительности может достигать нескольких порядков.

В вашей кодовой базе Python основные источники улучшений связаны с кодом, содержащим множество циклов и числовых элементов. Многоядерные операции — еще один источник прогресса, но эти методы оптимизации может быть трудно определить в чистом Python с GIL (Global Interpreter Lock). В этих случаях может подойти нативная оптимизация.

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

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

Новые ограничения

Есть много способов оптимизировать ваш код Python. Преимущества оптимизации вашего программного обеспечения очевидны: вы можете получить прирост производительности (ЦП, ОЗУ), снизить затраты на инфраструктуру и вычисления и, наконец, снизить выбросы углекислого газа (см., например, [1] Р. Перейра et al,изучение энергоэффективности нескольких языков программирования).

Но оптимизация кода добавляет некоторые новые ограничения в ваш рабочий процесс:

  • Вам нужно добавить новые зависимости (например, программные библиотеки, человеческие навыки). Эти новые зависимости могут снизить эффективность обслуживания.
  • Вам нужно изучить новые подходы (Cython, C/C++ или Rust) и поделиться ими со своей командой.
  • Новый язык накладывает дополнительные способы тестирования, интеграции и развертывания вашего кода.
  • Конечный программный артефакт может быть менее переносимым.

Как?

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

Чистый питон

Прежде чем интегрировать новую нативную зависимость в свою кодовую базу, обязательно сделайте краткий обзор богатой экосистемы Python. Может быть, существует собственная библиотека для вашего варианта использования? Например, вы можете использовать специализированные библиотеки, такие как NumPy или Pandas. Кроме того, вы можете использовать векторизованный код и отслеживать неоптимальные шаблоны в своем коде.

ПиПи

PyPy — оптимизирующий JIT-компилятор для языка Python. PyPy предлагает высокую совместимость с языком Python и его экосистемой. Вы можете использовать PyPy для запуска существующей программы Python; он может работать с полки с улучшением производительности.

Нумба

Numba — это компилятор JIT (точно в срок), напрямую интегрированный с интерпретатором Python. Numba предлагает набор декораторов Python, которые автоматически компилируют ваш код в высокопроизводительный нативный код LLVM. Синтаксис прост и состоит только из нескольких специализированных декораторов для пометки функции Python, скомпилированной с помощью Numba. Numba ориентирован на обработку чисел и легко интегрируется с NumPy и другими высокопроизводительными функциями (поточность, SIMD-векторизация, GPU).

Китон

Cython — это транспилятор, который компилирует подмножество Python, обогащенное типами, в оптимизированный код C или C++, интегрированный с CPython API. В результате сгенерированные библиотеки интегрируются со средой выполнения Python с повышением производительности. Основные библиотеки сообщества Python используют Cython из-за его простоты и быстрой интеграции с собственным кодом. Можно привести, например, библиотеки scikit-learn или SciPy, которые широко используют Cython.

C/C++

Вы можете разработать свой плагин на этих родных языках напрямую с помощью CPython API или с помощью генераторов привязок, таких как boost::python, pybind11 или CFFI. В этих случаях библиотека-оболочка делает всю неприятную сантехнику.

Ржавчина

Новичок, Rust — родной, ориентированный на высокую производительность язык. Вы можете использовать его для создания высокопроизводительных плагинов для Python с собственными привязками. Фреймворк PyO3 справляется с этой задачей в среде Rust.

Вы можете увидеть пример в следующей статье.



Краткий обзор

Заворачивать

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

Почему/что вы хотите оптимизировать?

Вам нужно готовое обновление производительности?

Если вам нужно небольшое улучшение производительности и вы не полагаетесь на несовместимые собственные библиотеки, вы можете использовать PyPy для повышения производительности.

Оптимизировать определенную часть кода Python?

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

Создать собственное расширение для Python?

Cython отлично подходит для этого. Но у вас будет код Python, чередующийся с кодом Cython, что может усложнить обслуживание программного обеспечения. Остерегайтесь спагетти-кода!

Предоставить нативную библиотеку Python?

Вы хотите интегрировать определенную функцию или обеспечить полную сквозную интеграцию с библиотекой? В зависимости от этого выбора вы можете использовать несколько подходов: Cython или генератор прямой привязки C/C++ являются ведущим выбором. Для нативной библиотеки Rust вы можете использовать систему PyO3.

Чтобы углубиться в эту тему, я тестирую несколько подходов в этом простом тесте.



Рекомендации

[1] Р. Перейра и др., «Энергоэффективность в разных языках программирования: как соотносятся энергия, время и память?», Материалы 10-й международной конференции ACM SIGPLAN по программному обеспечению. языковая инженерия, 2017 г., стр. 256–267.

Получайте удовольствие от написания кода на Python!