LINUX.ORG.RU

Эффективная оптимизация. Что такое Cython.

 , , , ,


13

7

За последнюю неделю почему-то часто всплывал этот вопрос, поэтому распишу всё сразу в одном месте. Описывать буду на примере питона, но общие принципы применимы ко многим языкам. Итак.

Часть первая. Правила эффективной оптимизация

  1. Не думайте об оптимизации пока код не дописан. Вообще! Думайте, как написать код проще и понятнее. А про оптимизацию даже не вспоминайте, пока не запустите код. Когда код запустится, проверьте насколько быстро он отрабатывает. Если он достаточно быстр — задача решена, ничего делать не нужно. Точка. И только если код отрабатывает медленнее, чем требуют условия задачи — начинайте думать об оптимизации.
  2. Попробуйте JIT, например PyPy, если условия это позволяют (apt-get install pypy && pypy yourprogram.py). Когда не хватает совсем немного производительности — JIT ускорит в несколько раз, и этого может быть достаточно. И тогда всё, задача решена. Только если это не сработало, переходите к фактической оптимизации кода.
  3. Пройдитесь профайлером. python -m cProfile yourprogram.py Никогда не оптимизируйте код без профилирования. По профилю определите самые медленные куски кода. Если код слишком большой — разбейте на функции. Не нужно бросаться переписывать всё подряд. Изолируйте наиболее прожорливые куски кода, и работайте только с ними.
  4. Выполните высокоуровневую оптимизацию найденных медленных кусков кода. Используйте более быстрые библиотеки: gmpy2 вместо встроенной длинной арифметики, python-regex вместо встроенного re, numpy для матричных вычислений, и т.д. Замените dict на list. Вынесите все возможные вычисления за циклы. Наконец, оптимизируйте алгоритм, или попробуйте найти ему более быстрый аналог. Если что-то получилось — goto 2.
  5. Cython. Расставьте типы, пройдитесь профайлером, посмотрите annotate cython-а, какой код сгенерирован, какие куски можно ускорить (он их расцвечивает)... Ещё раз подчёркиваю, низкоуровневая оптимизация — это последний этап, когда другие варианты исчерпаны.

Часть вторая. Cython

Если мы всё-таки дошли до cython-а, то... что же он такое?

Cython - это транслятор из питона в Си. Всё. Он просто генерирует код на си.

Если в файле mymodule.py написать:

def somefunc(x):
    y = x*42
    return y
и запустить cython mymodule.py то он том же каталоге сгенерирует mymodule.c, в котором будет что-то вроде:
static PyObject *__pyx_pf_8mymodule_somefunc(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_x) {
  PyObject *__pyx_v_y = NULL, *__pyx_r = NULL, *__pyx_t_1 = NULL;

  __pyx_t_1 = PyNumber_Multiply(__pyx_v_x, __pyx_int_42);
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_v_y = __pyx_t_1;
  __pyx_t_1 = 0;

  __Pyx_XDECREF(__pyx_r);
  __Pyx_INCREF(__pyx_v_y);
  __pyx_r = __pyx_v_y;

  __Pyx_XDECREF(__pyx_v_y);
  __Pyx_XGIVEREF(__pyx_r);
  return __pyx_r;
}
И, хотя это редко делают вручную, этот код можно собрать обычным компилятором: gcc -shared -O3 -o mymodule.so mymodule.c `python-config --cflags --ldflags`. Нигде в остальном коде ничего менять не надо. Обычный «import mymodule» загрузит бинарный module.so так же, как загрузил бы питоновый mymodule.py.

Да, cython позволяет скомпилировать питонокод. Но никаких глубоких интеллектуальных оптимизаций cython не делает. Он просто вызывает из libpython.so питоновые функции, такие как PyNumber_Multiply(). Без питона этот код работать не будет. (в принципе, его можно собрать статически, но обычно это не имеет смысла — реальная программа всё равно будет использовать кучу внешних либ, и ещё одна библиотека роли не сыграет)

Так как все вызовы питоновых функций остались, то просто сборка cython-ом большого ускорения не даст, может, раза в два. Но! Cython-у можно указать, где использовать сишные типы вместо питоновых! В примере выше, если расставить типы:

cdef double somefunc(double x):
    cdef double y = x*42
    return y
то cython mymodule.pyx сгенерирует в mymodule.c код:
static double __pyx_f_8mymodule_somefunc(double __pyx_v_x) {
  double __pyx_v_y, __pyx_r;

  __pyx_v_y = (__pyx_v_x * 42.0);

  __pyx_r = __pyx_v_y;

  return __pyx_r;
}
По сути, это чистый код на си. Быстрее некуда. А с параметром --annotate cython -a mymodule.pyx дополнительно сгенерирует «mymodule.html», в котором раскрасит код цветами. По нему легко смотреть, какие части кода ещё стоит оптимизировать. Но так как после расстановки типов обычным питоном такой код уже не запустится, его традиционно сохраняют в файле с расширением .pyx вместо .py.

Вот так, не написав ни одной строчки на си, а просто расставив типы, медленный питоновый код превращается в быстрый сишный.

В целом, это всё.

PS: Это не все возможности cython-а. В нём можно использовать плюсовые типы, например std::vector. Причём можно даже писать: cdef vector[double] sqrs = [x*x for x in somelist] и всё преобразование из питоновых типов в плюсовые и обратно cython возмёт на себя. Можно вызывать и внешний код на си (cdef extern from).

Есть и более тонкие оптимизации, например мелким функциям можно расставлять inline (хотя с этим и gcc обычно справляется). А ещё код, не использующий питоновые объекты, не блокирует GIL! А значит отлично подходит для многопоточных вычислений. В cython-е есть и модули для параллельных вычислений.

Да и сами .pyx файлы обычно компилируются не руками, а как часть скрипта distutils/setuptool. А в отладочных целях import pyximport; pyximport.install() и после этого обычный import mymodule сможет импортировать не только .py, но и .pyx файлы.

Полезные ссылки

Итого: Оптимизировать надо только когда иначе нельзя, и только то, что необходимо. Низкоуровневая оптимизация делается в последнюю очередь. Но если мы её таки делаем, то cython позволяет сделать её максимально легко — просто расставив типы.

PPS: Питон в этом не уникален. Почти во всех языки есть возможности низкоуровневой оптимизации, расширения на си через FFI/JNI/и т.д. Есть unsafe код в rust и c#. Даже в паскале и си есть ассемблерные вставки. Не удивительно, что кто-то придумал аналог и для питона. Так что эти же принципы оптимизации применимы и к другим языкам.

Ответ на: комментарий от t184256

достигает того же самого без сношений с системой сборки и без аннотаций типов внутри кода. В остальном у нее на сегодняшний день почти feature-parity с Cython + она содержит немного сбоку-припекнутый кодогенератор в CUDA/ROCm. На работе свалил с Cython на numba, не жалуюсь.

Скорее, numba соперничает с pypy (pybenchmarks: pypy vs numba). А догнать cython ей будет крайне трудно (pybenchmarks: cython vs numba). Cython транслируется в сишный код. Догнать или обогнать его можно только при наличии багов в gcc, не?

pynonymous
() автор топика
Ответ на: комментарий от pynonymous

Cython транслируется в сишный код. Догнать или обогнать его можно только при наличии багов в gcc, не?

Не. Numba транслируется в LLVM IR. По твоей логике, Cython у нее в хвосте плетется.

Бенчмарки отстой как минимум потому что сравнивают JIT-компиляцию с AOT-компиляцией. Результат предсказуем, че.

t184256 ★★★★★
()
Ответ на: комментарий от EXL

Почему бы просто не переписать прототип на Python’е на нормальный по быстродействию язык, если нужна хорошая оптимизация?

Если прототип маленький, и есть время и возможность его переписать, то, да, можно.

Просто питон очень экономит время. То, что на питоне делается за день, на плюсах можно неделю переписывать. А проблема часто только в тормозах одного куска кода. Вот в таком случае можно оптимизировать только этот кусок, и получить ту же скорость, сэкономив неделю переписывания.

Производительность не является для питона непреодолимой проблемой.

Я не говорю, что весь код в мире надо писать на python/cython. Я только говорю, что на питоне не только можно быстро писать код, но и можно писать быстрый код. А стоит так делать или нет — зависит от задачи.

pynonymous
() автор топика

На самом деле premature optimization - один из мифов разработки. Просто оптимизация она разная - есть алгоритмическая и структурная — ее надо делать сразу. Вот прямо в процессе написания и рефакторинга. А лучше еще на доске до кодинга, но тут при шибко быстрой разработке может не получиться. Потому, что если этого не делать, получается говно и тем людям, которые за тебя будут «оптимизировать» придется переписывать весь твой код нахрен. Это можно не делать только в одном случае - стадия прототипирования небольшого отдельного модуля. В случае же микрооптимизаций лучше их откладывать на конец, когда весь костяк кода уже готов, профилировщиками искать узкие места и действовать соответственно. Но они бесполезны при отсутствии алгоритмических и структурных оптимизаций.

Про cython - это микрооптимизация. На самом деле часто лучше помогает написать код модуля прямо на C. Просто cython вставляет тонны glue кода, который часто не слишком нужен. Разумеется, переписываемое место подскажут инструменты.

slapin ★★★★★
()
Ответ на: комментарий от t184256

Бенчмарки отстой как минимум потому что сравнивают JIT-компиляцию с AOT-компиляцией. Результат предсказуем, че.

Я это и имею ввиду. В cython-е машинный код получается AOT-компиляцией с полной информацией о типах. А в numba машинный код получается JIT-компиляцией, и пытается эти типы угадать, а заодно делать дополнительные проверки, на случай если угадали неверно. Как угадывающий и делающий дополнительные проверки код может быть быстрее кода, который этих проверок не делает?

pynonymous
() автор топика
Ответ на: комментарий от slapin

На самом деле часто лучше помогает написать код модуля прямо на C.

Так и делают. С какого чердака достали этот пыльный cython, я поражаюсь. Только ради того чтобы божественный питончик не сливал так эпично синиетические тестики? Лол.

bread
()
Ответ на: комментарий от bread

Ммм cython всё ещё питон вообще-то, +-. Там где у тебя 10000 строк си, у тебя 1000 строк цитона. А значит, в 10 раз меньше ошибок, да и разработка быстрее раз в 50. А производительность та же. Вывод?

anonymous
()
Ответ на: комментарий от anonymous

Зато эти 10000 строк напишет сишник так как ему удобно без нужды сношаться с сишкопитоном. А питонокодеру останется только сделать обвязку. Рассчитывать на то, что скриптоваятель сам себе будет эффективные либы клепать, как-то слишком оптимистично. Кстати, в гугле же пилили компилятор на манер cython, но чота закопали. И примерно тогда же появился го. Вот и ответ на потуги питонокомпиляния.

bread
()
Ответ на: комментарий от pynonymous

Как угадывающий и делающий дополнительные проверки код может быть быстрее кода, который этих проверок не делает?

Во-первых, угадывание сигнатуры в Numba необязательно, можешь прописать ее сам. Просто вы в своем сравнении ее не прописываете.

Во-вторых, Cython-код тоже компилируется, просто вы это в своем сравнении не замеряете. А в numba замеряете.

В третьих, проблемой это является только потому, что форумные воены опускают numba как тормоза, потому что в их игрушечных задачках она слила Cython как раз на время компиляции. А в реальных задачах это важно примерно никак, даже 15 секунд компиляции как слону дробина. Вот только что досчиталась задача по работе, которая молотила numboй 27 часов.

t184256 ★★★★★
()
Последнее исправление: t184256 (всего исправлений: 1)
Ответ на: комментарий от anonymous

А на Cython это было полгода назад и это было 5 суток, потому что не на GPU.

t184256 ★★★★★
()

6. Бросьте заниматься этой хернёй и перепишите на си или плюсах.
/thread

crutch_master ★★★★★
()
Ответ на: Про программирование и кодинг от Crocodoom

чёт у тебя каша в голове. сложность алгоритмов смешалась с эффективностью, качеством и тем, что должна делать программа. а ещё, ты считаешь, что в программе не должно быть багов. лiл.
«базовое знание Computer Science позволит»… нихера. нихера базовое знание не позволит, разве, что такие посты писать, «диванно-академические»

system-root ★★★★★
()
Ответ на: комментарий от anonymous

А значит, в 10 раз меньше ошибок, да и разработка быстрее раз в 50. А производительность та же. Вывод?

https://www.youtube.com/watch?v=F2skk6RFFos

https://www.youtube.com/watch?v=cdX8r3ZSzN4

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 1)
Ответ на: комментарий от system-root

а ещё, ты считаешь, что в программе не должно быть багов. лiл.

Ок! Так и запишу

system-root ★★★ (18.02.2019 5:17:41) Считает, что в программах должны быть баги

Crocodoom ★★★★★
()
Ответ на: Про программирование и кодинг от Crocodoom

Программист/кодер - очень относительно, образно, у каждого свое понимание... topcoder например это совсем не про кодстайл с комментариями :).

Но соглашусь, что в постановке ТС оно звучит ближе к «быдлокодинг».

ei-grad ★★★★★
()
Ответ на: комментарий от WitcherGeralt

а доводилось ли Геральту проектировать умеренно-большие и сложные проекты? если доводилось, Геральт наверняка знает зачем в проектировании используют прототипы.

еще раз с ударением - прототипы это часть процесса проектирования.

val-amart ★★★★★
()
Ответ на: комментарий от val-amart

Как сказанное тобой противоречит сказанному мной? И к чему это вообще?

Прототипы я пишу.

WitcherGeralt ★★
()
Ответ на: комментарий от val-amart

Откуда вы лезете, динозавры? Какие еще прототипы с agile. Кто сейчас станет ждать пока вы обкатаете прототип на пистоне и перепишите его, просто курам на смех. Выкатить «прототип» сразу в продакшн, и потом возможно когда-нибудь что-то переписать, разве что так.

bread
()
Ответ на: комментарий от t184256

Во-первых, угадывание сигнатуры в Numba необязательно, можешь прописать ее сам. Просто вы в своем сравнении ее не прописываете.

В моих тестах угадывалась она довольно неплохо. Явное прописывание этих сигнатур код не ускоряло.

Во-вторых, Cython-код тоже компилируется, просто вы это в своем сравнении не замеряете. А в numba замеряете.

В своем сравнении WitcherGeralt это как раз учёл. В тесте pybenchmarks: Numba vs Cython на тех нескольких тестах, где результат есть, они либо примерно равны, либо Numba отстаёт настолько, что даже вычитание времени компиляции этого не изменит.

А в реальных задачах это важно примерно никак, даже 15 секунд компиляции как слону дробина.

Просто по вашим же словам получается, что множество таких реальных задач для Numba довольно ограничено: только математика и только с numpy.

А в целом я не возражаю против использования Numba. Её, конечно, стоит проверять, а то у меня она на одном тесте упала, на другом выдала неверный результат... Но в остальном, когда она работает, это — отличный JIT для п.2 из топика. Дополнительный бонус ей — умение запускать код на GPU. Пусть пилят...

PS: непонятно, почему numba требует редактирования кода и расстановки декораторов, а не применяется ко всему коду без изменений, как pypy.

pynonymous
() автор топика
Ответ на: комментарий от pynonymous

Просто по вашим же словам получается, что множество таких реальных задач для Numba довольно ограничено: только математика и только с numpy.

Не совсем, но близко. И снаружи может тебе этот круг кажется маленьким, а мне изнутри — довольно большим.

PS: непонятно, почему numba требует редактирования кода и расстановки декораторов, а не применяется ко всему коду без изменений, как pypy.

Вполне понятно. PyPy — отдельный интерпретатор, а более гибкая Numba — скорее библиотека для выборочной компиляции/оффлоадинга. Декорировать numba.jitом «не-математику» и не будут, это только на ЛОРе почему-то пробуют.

t184256 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.