Прототипирование с помощью кода Python перед компиляцией

Некоторое время я думал над написанием библиотеки, подходящей для пиковых значений. Я достаточно хорошо знаю Python и планирую реализовать все на Python с самого начала, но предполагаю, что в конечном итоге мне, возможно, придется заново реализовать некоторые основные процедуры на скомпилированном языке.

IIRC, одна из оригинальных версий Python, была языком прототипов, однако Python довольно либерален в том, что позволяет передавать функции, функторы, объекты в функции и методы, тогда как я подозреваю, что то же самое не относится, скажем, к C или Fortran.

Что я должен знать о разработке функций / классов, которые, как я предполагаю, должны будут взаимодействовать с скомпилированным языком? И сколько из этих потенциальных проблем решается такими библиотеками, как cTypes, bgen, SWIG , Boost.Python , Cython или Python SIP?

Для этого конкретного варианта использования (подходящая библиотека) я представляю, как разрешить пользователям определять математические функции (Гуассиан, Лоренциан и т. Д.) Как функции Python, которые затем могут быть переданы для интерпретации с помощью скомпилированной библиотеки подгонки кода. Передача и возврат массивов также важны.

Ответов (7)

Решение

Наконец, вопрос, на который я действительно могу дать ценный ответ :).

Я исследовал f2py, boost.python, swig, cython и pyrex для своей работы (доктор философии в области оптических методов измерения). Я широко использовал swig, некоторые из boost.python, а также pyrex и cython. Я также использовал ctypes. Это моя поломка:

Отказ от ответственности : это мой личный опыт. Я не участвую ни в одном из этих проектов.

swig: плохо работает с c ++. Так и должно быть, но на этапе связывания названия проблем с искажением имен были для меня большой головной болью в Linux и Mac OS X. Если у вас есть код C и вы хотите, чтобы он был связан с python, это хорошее решение. Я обернул GTS для своих нужд, и мне нужно было написать в основном разделяемую библиотеку C, к которой я мог бы подключиться. Я бы не рекомендовал это.

Ctypes: я написал оболочку libdc1394 (библиотека камеры IEEE), используя ctypes, и это было очень простым опытом. Вы можете найти код на https://launchpad.net/pydc1394 . Преобразование заголовков в код Python - это большая работа, но тогда все работает надежно. Это хороший способ, если вы хотите связать внешнюю библиотеку. Ctypes также находится в stdlib Python, поэтому каждый может сразу использовать ваш код. Это также хороший способ быстро поиграться с новой библиотекой в ​​python. Я могу порекомендовать его для взаимодействия с внешними библиотеками.

Boost.Python : Очень приятно. Если у вас уже есть собственный код C++, который вы хотите использовать в Python, сделайте это. Таким образом очень легко преобразовать структуры классов C++ в структуры классов Python. Я рекомендую его, если у вас есть код C++, который вам нужен на python.

Pyrex / Cython: используйте Cython, а не Pyrex. Период. Cython более продвинутый и более приятный в использовании. В настоящее время я делаю с cython все, что раньше делал с SWIG или Ctypes. Это также лучший способ, если у вас слишком медленный код Python. Процесс абсолютно фантастический: вы конвертируете свои модули python в модули cython, создаете их и продолжаете профилировать и оптимизировать, как будто это все еще был python (менять инструменты не нужно). Затем вы можете применить столько (или меньше) кода C, смешанного с вашим кодом Python. Это намного быстрее, чем необходимость переписывать целые части вашего приложения на C; вы только переписываете внутренний цикл.

Тайминги : ctypes имеет самую высокую нагрузку вызовов (~ 700ns), а затем boost.python (322ns), то непосредственно глотка (290ns). Cython имеет самые низкие накладные расходы на вызовы (124 нс) и лучшую обратную связь, на которую он тратит время (поддержка cProfile!). Числа взяты из моего ящика, вызывающего тривиальную функцию, возвращающую целое число из интерактивной оболочки; поэтому накладные расходы на импорт модуля не рассчитываются по времени, учитываются только накладные расходы на вызов функции. Поэтому проще и продуктивнее всего быстро получить код Python путем профилирования и использования cython.

Резюме : для вашей проблемы используйте Cython;). Я надеюсь, что это краткое изложение будет полезно для некоторых людей. Я с радостью отвечу на любой оставшийся вопрос.


Изменить : я забыл упомянуть: для числовых целей (то есть для подключения к NumPy) используйте Cython; у них есть поддержка (потому что они в основном разрабатывают cython для этой цели). Так что это должен быть еще один +1 для вашего решения.

В дополнение к перечисленным выше инструментам я могу порекомендовать использовать Pyrex (для создания модулей расширения Python) или Psyco (в качестве JIT-компилятора для Python).

По моему опыту, есть два простых способа вызвать код C из кода Python. Есть и другие подходы, которые более утомительны и / или многословны.

Первый и самый простой - скомпилировать кучу кода C как отдельную разделяемую библиотеку, а затем вызывать функции в этой библиотеке с помощью ctypes. К сожалению, передача чего-либо, кроме базовых типов данных, нетривиально.

Второй самый простой способ - написать модуль Python на C, а затем вызывать функции в этом модуле. Вы можете передать все, что хотите, этим функциям C, не прыгая через какие-либо обручи. И из этих функций C легко вызывать функции или методы Python, как описано здесь: https://docs.python.org/exnding/exnding.html#calling-python-functions-from-c

У меня недостаточно опыта работы со SWIG, чтобы давать толковые комментарии. И хотя можно делать такие вещи, как передача пользовательских объектов Python в функции C через ctypes или определение новых классов Python в C, эти вещи раздражают и многословны, и я рекомендую использовать один из двух подходов, описанных выше.

Лучший способ спланировать возможный переход к скомпилированному коду - это написать чувствительные к производительности части в виде модуля простых функций в функциональном стиле (без состояния и без побочных эффектов), которые принимают и возвращают базовые типы данных.

Это обеспечит взаимно-однозначное сопоставление кода вашего прототипа Python с конечным скомпилированным кодом и позволит вам легко использовать ctypes и избежать целой кучи головной боли.

Для подгонки пиков вам почти наверняка потребуется использовать массивы, что немного усложнит ситуацию, но по-прежнему вполне выполнимо с ctypes.

Если вы действительно хотите использовать более сложные структуры данных или изменить переданные аргументы, SWIG или стандартный интерфейс C-extension Python позволит вам делать то, что вы хотите, но с некоторыми хлопотами.

За то , что вы делаете, вы также можете проверить NumPy , которые могли бы сделать некоторые работы , которую вы хотели бы нажать на C, а также предлагает дополнительную помощь в перемещении данных вперед и назад между Python и C .

Я не использовал SWIG или SIP, но считаю, что написание оболочки Python с помощью boost.python является очень мощным и относительно простым в использовании.

Я не понимаю, каковы ваши требования для передачи типов между C/C++ и python, но вы можете легко сделать это, либо предоставив тип C++ для python, либо используя общий аргумент boost :: python :: object для вашего C++ API. Вы также можете зарегистрировать конвертеры для автоматического преобразования типов Python в типы C++ и наоборот.

Если вы планируете использовать boost.python, это руководство - хорошее место для начала.

Я реализовал нечто похожее на то, что вам нужно. У меня есть функция C++, которая принимает функцию python и изображение в качестве аргументов и применяет функцию python к каждому пикселю изображения.

Image* unary(boost::python::object op, Image& im)
{
    Image* out = new Image(im.width(), im.height(), im.channels());
    for(unsigned int i=0; i<im.size(); i++)
    {
        (*out)[i] == extract<float>(op(im[i]));
    }
    return out;
}

В этом случае Image - это объект C++, доступный для python (изображение с плавающими пикселями), а op - это функция, определяемая python (или действительно любой объект python с атрибутом __call__). Затем вы можете использовать эту функцию следующим образом (при условии, что унарный находится в вызываемом изображении, которое также содержит изображение и функцию загрузки):

import image
im = image.load('somefile.tiff')
double_im = image.unary(lambda x: 2.0*x, im)

Что касается использования массивов с ускорением, я лично этого не делал, но я знаю, что функция предоставления массивов для python с помощью ускорения доступна - это может быть полезно.

Python довольно либерален в том, что позволяет передавать функции, функторы и объекты функциям и методам, в то время как я подозреваю, что то же самое нельзя сказать о C или Fortran.

В C вы не можете передать функцию в качестве аргумента функции, но вы можете передать указатель на функцию, который является такой же хорошей функцией.

Я не знаю, насколько это поможет, когда вы попытаетесь интегрировать код C и Python, но я просто хотел прояснить одно заблуждение.

f2py (часть numpy ) - более простая альтернатива SWIG и boost.python для обертывания кода C / Fortran для обработки чисел.