Добавление Cython в SciPy#
Как написано на Сайт Cython:
Cython — это оптимизирующий статический компилятор как для языка программирования Python, так и для расширенного языка программирования Cython (основанного на Pyrex). Он делает написание C расширений для Python таким же простым, как и сам Python.
Если ваш код в настоящее время выполняет много циклов в Python, он может получить выгоду от компиляции с помощью Cython. Этот документ предназначен для очень краткого введения: достаточно, чтобы увидеть, как использовать Cython с SciPy. Как только ваш код будет компилироваться, вы можете узнать больше о том, как оптимизировать его, ознакомившись с Документация Cython.
Есть только две вещи, которые нужно сделать, чтобы SciPy скомпилировал ваш код с помощью Cython:
Включите свой код в файл с
.pyxрасширение, а не.pyрасширение. Все файлы с.pyxрасширения автоматически преобразуются Cython в.cили.cppфайлы при сборке SciPy.Добавить новый
.pyxфайл вmeson.buildконфигурация сборки подпакета, в котором находится ваш код. Обычно уже есть другие.pyxприсутствующие шаблоны (если нет, ищите в другом подмодуле), так что есть пример для следования тому, какой именно контент добавить вmeson.build.
Пример#
scipy.optimize._linprog_rs.py содержит реализацию пересмотренного симплекс-метода для scipy.optimize.linprog. Пересмотренный
симплекс-метод выполняет множество элементарных операций со строками матриц, и
поэтому он был естественным кандидатом для Cython-оптимизации.
Обратите внимание, что scipy/optimize/_linprog_rs.py импортирует BGLU и
LU классы из ._bglu_dense точно так же, как если бы они были обычными
классами Python. Но это не так. BGLU и LU являются классами Cython,
определёнными в /scipy/optimize/_bglu_dense.pyx. Ничего в способе их импорта или использования не указывает на то, что они написаны на Cython; единственный способ определить, что это классы Cython, заключается в том, что они определены в файле с расширением .pyx расширение.
Даже в /scipy/optimize/_bglu_dense.pyx, большая часть кода напоминает Python. Наиболее заметные различия — наличие cimport,
cdef, и Cython декораторы. Ни один из них не является строго необходимым. Без них чистый Python-код все еще может быть скомпилирован Cython. Расширения языка Cython — это *просто* настройки для улучшения производительности. .pyx файл автоматически преобразуется в .c
файл Cython при сборке SciPy.
Осталось только добавить конфигурацию сборки, которая будет выглядеть примерно так:
_bglu_dense_c = opt_gen.process('_bglu_dense.pyx')
py3.extension_module('_bglu_dense',
_bglu_dense_c,
c_args: cython_c_args,
dependencies: np_dep,
link_args: version_link_args,
install: true,
subdir: 'scipy/optimize'
)
При сборке SciPy, _bglu_dense.pyx будет транспилирован cython
в код C, а затем этот сгенерированный C-файл обрабатывается Meson как любой другой C-код в SciPy - создавая модуль расширения, который мы сможем импортировать и использовать LU и BGLU классы из.
Упражнение#
Посмотрите видео с пошаговым выполнением этого упражнения: Cython-изация кода SciPy
Обновить Cython и создать новую ветку (например,
git checkout -b cython_test) для внесения некоторых экспериментальных изменений в SciPyДобавить простой код на Python в
.pyфайл в/scipy/optimizeдиректория, например/scipy/optimize/mypython.py. Например:def myfun(): i = 1 while i < 10000000: i += 1 return i
Давайте посмотрим, сколько времени занимает этот чистый Python-цикл, чтобы сравнить производительность Cython. Например, в IPython-консоли в Spyder:
from scipy.optimize.mypython import myfun %timeit myfun()
Я получаю что-то вроде:
715 ms ± 10.7 ms per loop
Сохраните ваш
.pyфайл в.pyxфайл, напримерmycython.pyx.Добавьте
.pyxtoscipy/optimize/meson.build, способом, описанным в предыдущем разделе.Пересоберите SciPy. Обратите внимание, что модуль расширения (
.soили.pydфайл) был добавлен вbuild/scipy/optimize/каталог.Измерить время, например, перейдя в IPython с
python dev.py ipythonи затем:from scipy.optimize.mycython import myfun %timeit myfun()
Я получаю что-то вроде:
359 ms ± 6.98 ms per loop
Cython ускорил чистый Python-код примерно в 2 раза.
Это незначительное улучшение в общей схеме. Чтобы понять почему, полезно, чтобы Cython создал "аннотированную" версию нашего кода для отображения узких мест. В окне терминала вызовите Cython на вашем
.pyxфайл с-aфлаг:cython -a scipy/optimize/mycython.pyx
Обратите внимание, что это создает новый
.htmlфайл в/scipy/optimizeкаталог. Откройте.htmlфайл в любом браузере.Желто-подсвеченные строки в файле указывают на потенциальное взаимодействие между скомпилированным кодом и Python, что значительно замедляет работу. Интенсивность подсветки указывает на предполагаемую серьезность взаимодействия. В данном случае, большая часть взаимодействия может быть устранена, если мы определим переменную
iкак целое число, чтобы Cython не рассматривал возможность того, что это общий объект Python:def myfun(): cdef int i = 1 # our first line of Cython code while i < 10000000: i += 1 return i
Воссоздание аннотированного
.htmlфайл показывает, что большая часть взаимодействия с Python исчезла.Пересоберите SciPy, откройте новый консоль IPython и
%timeit:
from scipy.optimize.mycython import myfun
%timeit myfun()
Я получаю что-то вроде: 68.6 ns ± 1.95 ns per loop. Код на Cython работал
примерно в 10 миллионов раз быстрее, чем исходный код на Python.
В этом случае компилятор, вероятно, оптимизировал цикл, просто возвращая конечный результат. Такое ускорение не типично для реального кода, но это упражнение определённо иллюстрирует мощь Cython, когда альтернативой являются многие низкоуровневые операции в Python.