Cython: более чем 30-кратное ускорение Python-кода

в 9:30, , рубрики: cython, python, Блог компании RUVDS.com, разработка, Разработка веб-сайтов

Python — это язык, который любят многие программисты. Этим языком невероятно легко пользоваться. Всё дело в том, что код, написанный на Python, отличается интуитивной понятностью и хорошей читабельностью. Однако в разговорах о Python часто можно слышать одну и ту же жалобу на этот язык. Особенно тогда, когда о Python говорят знатоки языка C. Вот как она звучит: «Python — это медленно». И те, кто так говорят, не грешат против истины.

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

Cython: более чем 30-кратное ускорение Python-кода - 1

Есть несколько способов ускорения 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 в своих проектах?

Cython: более чем 30-кратное ускорение Python-кода - 2

Автор: ru_vds

Источник


* - обязательные к заполнению поля