Хотите повысить производительность Python? Это проще, чем вы думаете

Общепризнанным фактом является то, что Python стал одним из самых популярных языков программирования в мире. С 1980-х годов, когда Гвидо ван Россум возглавил разработку языка, он добился огромного успеха на протяжении четырех десятилетий. Я считаю, что одной из причин этого является то, что он поддерживается большим сообществом и популярен среди крупных компаний, таких как Google.

Как мультипарадигмальный язык программирования python предлагает потрясающие возможности, такие как объектно-ориентированное программирование и структурированное программирование [1], Python использует динамическую типизацию и комбинацию подсчета ссылок и обнаружения циклов. сборщик мусора для управления памятью [2].

Из-за этой универсальности Python использовался разработчиками для создания ряда библиотек, особенно для аналитики и машинного обучения. Согласно Индексу сообщества программистов TIOBE, Python входит в десятку лучших языков с 2003 года. Однако если мы посмотрим на тенденцию, то сейчас мы живем в 2022 году, а Python — самый популярный язык.

Картина может показаться завершенной, но так ли это? Достаточно ли возможностей Python для создания мощных вычислительных продуктов? Как пишут Доминика Решке и Миха Юнгевич в своем блоге c-for-machine-learning-is-it-better-than-python-comparison:«Часто, когда Python, обычно более продуктивный для программирования более высокого уровня, достигает своих пределов производительности, и вы упираетесь в кирпичную стену, единственным выходом может быть переход на C++. “

Это пример NumPy, одного из самых популярных инструментов для специалистов по данным. NumPy — это модуль Python, который использует C и C++ для повышения производительности. Ниже приведен скриншот официальной страницы NumPy на GitHub.

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

Если вы разрабатываете пользовательскую библиотеку на основе Python для решения очень специфической проблемы, но производительность библиотеки низкая, есть ли у вас возможность решить проблему с производительностью? В этой статье мы рассмотрим один из методов включения улучшенных функций C++ в нашу пользовательскую библиотеку Python, чтобы упростить интеграцию и создание устанавливаемого пакета с использованием Pybind11 [3] (см. Документацию по pybind11 для более подробной информации о Pybind11).

Для наглядности давайте создадим пример за 3 простых шага:

Шаг 1: Создайте базовую функцию Python (py_func), которая принимает значения в диапазоне от 0 до указанного целого числа и выводит сумму квадратов. Сохраните документ как test.py и поместите его в папку pbex_py.

Шаг 2: Создайте идентичную функцию на C++ (c_func), а затем используйте pybind11 для ее выполнения на python. Создайте файл с именем test c.cpp и сохраните его в каталоге c.

Мы используем PYBIND11_MODULE для определения функции pbex, которая будет импортирована из python, а вторым аргументом, который нужно указать, является m, который будет компилировать нашу C++ функцию c_func.

Шаг 3: Чтобы упаковать наш код, нам нужно создать __init__.py и setup.py.

В setup.py есть два вида настроек. Во-первых, мы объявляем нашу функцию Python, указав packages и package_dir. Во-вторых, мы создаем общий объект для нашей функции C++, добавляя его в ext_modules, и снабжаем его cmdclass, который создает это расширение. [4]

Теперь наш полный каталог пакетов будет выглядеть так.

Мы установим этот пакет в нашей среде Python.

Установив наш пакет, мы сможем найти его в каталоге site-packages нашей среды.

Важно отметить, что файл pbex.cpython-310-x86_64.so, который является общим объектом, созданным для нашей функции C++, может быть импортирован как pbex а также pbex_py, которая является импортируемой функцией Python.

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

Очевидно, что мы можем интегрировать функции C++ в пакеты библиотек Python в качестве расширений и получать ошеломляющие результаты. Например, для 10 000 циклов среднее время для функции C составляет 549 нс (т. е. 0,596 мкс), а среднее время для функции Python составляет 80 мкс, из чего следует, что для этого примера функция C++ почти в 160 раз быстрее, чем функция Python.

Кроме того, мы можем воспользоваться другими методами для повышения производительности кода Python. Например, Cython (расширение C для Python) или воспользоваться преимуществами некоторых готовых решений, таких как Numba.

Полный код доступен на странице kudeore/pybind11: Сравнение C++ и Python (github.com).

Ссылки:

1. https://docs.python.org/3.0/reference/datamodel.html#названия-специальных-методов

2. https://docs.python.org/3/extending/extending.html#reference-counts

3. https://pybind11.readthedocs.io/ru/stable/basics.html

4. https://github.com/pybind/python_example