Cython — an overview¶
[Cython] is a programming language that makes writing C extensions for the Python language as easy as Python itself. It aims to become a superset of the [Python] language which gives it high-level, object-oriented, functional, and dynamic programming. Its main feature on top of these is support for optional static type declarations as part of the language. The source code gets translated into optimized C/C++ code and compiled as Python extension modules. This allows for both very fast program execution and tight integration with external C libraries, while keeping up the high programmer productivity for which the Python language is well known.
The primary Python execution environment is commonly referred to as CPython, as it is written in C. Other major implementations use Java (Jython [Jython] ), C# (IronPython [IronPython] ) and Python itself (PyPy [PyPy] ). Written in C, CPython has been conducive to wrapping many external libraries that interface through the C language. It has, however, remained non trivial to write the necessary glue code in C, especially for programmers who are more fluent in a high-level language like Python than in a close-to-the-metal language like C.
Originally based on the well-known Pyrex [Pyrex] , the Cython project has approached this problem by means of a source code compiler that translates Python code to equivalent C code. This code is executed within the CPython runtime environment, but at the speed of compiled C and with the ability to call directly into C libraries. At the same time, it keeps the original interface of the Python source code, which makes it directly usable from Python code. These two-fold characteristics enable Cython’s two major use cases: extending the CPython interpreter with fast binary modules, and interfacing Python code with external C libraries.
While Cython can compile (most) regular Python code, the generated C code usually gains major (and sometime impressive) speed improvements from optional static type declarations for both Python and C types. These allow Cython to assign C semantics to parts of the code, and to translate them into very efficient C code. Type declarations can therefore be used for two purposes: for moving code sections from dynamic Python semantics into static-and-fast C semantics, but also for directly manipulating types defined in external libraries. Cython thus merges the two worlds into a very broadly applicable programming language.
Cython: более чем 30-кратное ускорение Python-кода
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
При работе с функциями нам доступны следующие типы:
- def — обычная Python-функция, вызывается только из Python.
- cdef — Cython-функция, которую нельзя вызвать из обычного Python-кода. Такие функции можно вызывать только в пределах Cython-кода.
- cpdef — Функция, доступ к которой можно получить и из C, и из 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 в своих проектах?
При подготовке материала использовались источники:
https://docs.cython.org/en/latest/src/quickstart/overview.html
https://habr.com/ru/companies/ruvds/articles/462487/