Python — это язык, который любят многие программисты. Этим языком невероятно легко пользоваться. Всё дело в том, что код, написанный на Python, отличается интуитивной понятностью и хорошей читабельностью. Однако в разговорах о Python часто можно слышать одну и ту же жалобу на этот язык. Особенно тогда, когда о Python говорят знатоки языка C. Вот как она звучит: «Python — это медленно». И те, кто так говорят, не грешат против истины.
В сравнении со многими другими языками программирования Python — это, и правда, медленно. Вот результаты испытаний, в ходе которых сопоставляется производительность разных языков программирования при решении различных задач.
Есть несколько способов ускорения Python-программ. Например, можно применять библиотеки, рассчитанные на использование нескольких ядер процессора. Тем, кто работает с Numpy, Pandas или Scikit-Learn, можно посоветовать взглянуть на программный комплекс Rapids, позволяющий задействовать GPU при проведении научных расчётов.
Все эти методики ускорения работы хороши в тех случаях, когда решаемые с помощью Python задачи могут быть распараллелены. Например — это задачи по предварительной обработке данных или операции с матрицами.
Но как быть в том случае, если ваш код — это чистый Python? Что если у вас есть большой цикл for
, который вам совершенно необходимо использовать, и выполнение которого просто нельзя распараллелить из-за того, что обрабатываемые в нём данные должны обрабатываться последовательно? Можно ли как-то ускорить сам Python?
Ответ на этот вопрос даёт Cython — проект, используя который можно значительно ускорить код, написанный на Python.
Что такое Cython?
Cython, по своей сути, это промежуточный слой между Python и C/C++. Cython позволяет писать обычный Python-код с некоторыми незначительными модификациями, который затем напрямую транслируется в C-код.
Единственное изменение Python-кода при этом заключается в добавлении к каждой переменной информации об её типе. При написании обычного кода на Python переменную можно объявить так:
x = 0.5
При использовании Cython при объявлении переменной нужно указать её тип:
cdef float x = 0.5
Эта конструкция сообщает Cython о том, что переменная представляет собой число с плавающей точкой. По такому же принципу объявляют переменные и в C. При использовании обычного Python типы переменных определяются динамически. Явное объявление типов, применяемое в Cython — это то, что делает возможным преобразование Python-кода в C-код. Дело в том, что в C необходимо явное объявление типов переменных.
Установка Cython предельно проста:
pip install cython
Типы в Cython
При использовании Cython можно выделить два набора типов. Один — для переменных, второй — для функций.
Если речь идёт о переменных, то тут нам доступны следующие типы:
cdef int a, b, c
cdef char *s
cdef float x = 0.5
(число одинарной точности)cdef double x = 63.4
(число двойной точности)cdef list names
cdef dict goals_for_each_play
cdef object card_deck
Обратите внимание на то, что тут, фактически, показаны типы C/C++!
При работе с функциями нам доступны следующие типы:
def
— обычная Python-функция, вызывается только из Python.cdef
— Cython-функция, которую нельзя вызвать из обычного Python-кода. Такие функции можно вызывать только в пределах Cython-кода.cpdef
— Функция, доступ к которой можно получить и из C, и из Python.
Теперь, когда мы разобрались с типами Python, можно заняться ускорением Python-кода.
Ускорение кода с использованием Cython
Начнём с создания Python-бенчмарка. Это будет цикл for
, в котором выполняется вычисление факториала числа. Соответствующий код на чистом Python будет выглядеть так:
def test(x):
y = 1
for i in range(1, x+1):
y *= i
return y
Cython-эквивалент этой функции очень похож на её исходный вариант. Соответствующий код нужно поместить в файл с расширением .pyx
. Единственное изменение, которое нужно внести в код, заключается в добавлении в него сведений о типах переменных и функции:
cpdef int test(int x):
cdef int y = 1
cdef int i
for i in range(1, x+1):
y *= i
return y
Обратите внимание на то, что перед функцией стоит ключевое слово cpdef
. Это позволяет вызывать данную функцию из Python. Кроме того, тип назначен и переменной i
, играющей роль счётчика цикла. Не будем забывать о том, что типизировать нужно все переменные, объявленные в функции. Это позволит компилятору C узнать о том, какие именно типы ему использовать.
Теперь создадим файл setup.py
, который поможет нам преобразовать Cython-код в C-код:
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize('run_cython.pyx'))
Выполним компиляцию:
python setup.py build_ext --inplace
Теперь С-код готов к использованию.
Если взглянуть в папку, в которой находится Cython-код, там можно будет найти все файлы, необходимые для запуска C-кода, включая файл run_cython.c
. Если вам интересно — откройте этот файл и посмотрите на то, какой С-код сгенерировал Cython.
Теперь всё готово к тестированию нашего сверхбыстрого C-кода. Ниже приведён код, используемый для тестирования и сравнения двух вариантов программы.
import run_python
import run_cython
import time
number = 10
start = time.time()
run_python.test(number)
end = time.time()
py_time = end - start
print("Python time = {}".format(py_time))
start = time.time()
run_cython.test(number)
end = time.time()
cy_time = end - start
print("Cython time = {}".format(cy_time))
print("Speedup = {}".format(py_time / cy_time))
Код этот устроен очень просто. Мы импортируем необходимые файлы — так же, как импортируются обычные Python-файлы, после чего вызываем соответствующие функции, делая это так же, как если бы мы всё время работали бы с обычными Python-функциями.
Взгляните на следующую таблицу. Можно заметить, что Cython-версия программы оказывается быстрей её Python-версии во всех случаях. Чем масштабнее задача — тем больше и ускорение, которое даёт использование Cython.
Число | Показатель Python Time | Показатель Cython Time | Показатель Speedup |
10 | 1.6689300537109375e-06 | 4.76837158203125e-07 | 3.5 |
100 | 3.337860107421875e-06 | 4.76837158203125e-07 | 7.0 |
1000 | 2.193450927734375e-05 | 9.5367431640625e-07 | 23.0 |
10000 | 0.0002090930938720703 | 6.4373016357421875e-06 | 32.481 |
100000 | 0.0021562576293945312 | 6.008148193359375e-05 | 35.89 |
1000000 | 0.02128767967224121 | 0.0005953311920166016 | 35.75 |
10000000 | 0.2148280143737793 | 0.00594782829284668 | 36.1187317112278 |
Итоги
Использование Cython позволяет значительно ускорить практически любой код, написанный на Python, не прилагая к этому особенных усилий. Чем больше в программе циклов и чем больше данных она обрабатывает — тем лучших результатов можно ждать от применения Cython.
Уважаемые читатели! Используете ли вы Cython в своих проектах?
Автор: ru_vds