Обмен структурированными данными между Haskell и C - PullRequest
22 голосов
/ 21 декабря 2010

Во-первых, я новичок в Haskell.

Я планирую интегрировать Haskell в C для игры в реальном времени.Haskell делает логику, C делает рендеринг.Чтобы сделать это, я должен передавать огромные сложно структурированные данные (состояние игры) друг другу для каждого тика (не менее 30 раз в секунду).Таким образом, передаваемые данные должны быть легкими.Эти данные состояния могут располагаться в последовательном пространстве в памяти.Обе части Haskell и C должны иметь свободный доступ ко всем областям состояний.

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

Я читаю FFI Haskell (http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs) Посмотрите на код Haskell, явно указывающий расположение памяти.

У меня есть несколько вопросов.

  1. Может ли Haskell явно указывать макет памяти? (Для точного сопоставления со структурой C)
  2. Является ли это реальным макетом памяти? Или требуется какой-либо вид преобразования (снижение производительности)
  3. Если Q # 2 имеет значение true, любое снижение производительности при явном указании макета памяти?
  4. Каков синтаксис #{alignment foo}? Где я могу найти документ об этом?
  5. ЕслиЯ хочу передать огромные данные с наилучшей производительностью, как мне это сделать?

* PS Явная функция разметки памяти, о которой я сказал, это просто атрибут C # [StructLayout], который указывает позицию в памяти иРазмер явно. http://www.developerfusion.com/article/84519/mastering-structs-in-c/

Я не уверен, что Haskell имеет сопоставление лингвистической конструкции с полями структуры C.

Ответы [ 3 ]

24 голосов
/ 22 декабря 2010

Я настоятельно рекомендую использовать препроцессор.Мне нравится c2hs, но hsc2hs очень распространен, потому что он включен в ghc.Гринкарта, похоже, заброшена.

Чтобы ответить на ваши вопросы:

1) Да, через определение сохраняемого экземпляра.Использование Storable является единственным безопасным механизмом для передачи данных через FFI.Экземпляр Storable определяет, как выполнить маршалинг данных между типом Haskell и необработанной памятью (либо Haskell Ptr, ForeignPtr, либо StablePtr, либо указатель C).Вот пример:

data PlateC = PlateC {
  numX :: Int,
  numY :: Int,
  v1   :: Double,
  v2   :: Double } deriving (Eq, Show)

instance Storable PlateC where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = {#sizeof PlateC#}
  peek p =
    PlateC <$> fmap fI ({#get PlateC.numX #} p)
           <*> fmap fI ({#get PlateC.numY #} p)
           <*> fmap realToFrac ({#get PlateC.v1 #} p)
           <*> fmap realToFrac ({#get PlateC.v2 #} p)
  poke p (PlateC xv yv v1v v2v) = do
    {#set PlateC.numX #} p (fI xv)
    {#set PlateC.numY #} p (fI yv)
    {#set PlateC.v1 #}   p (realToFrac v1v)
    {#set PlateC.v2 #}   p (realToFrac v2v)

Фрагменты {# ... #} являются кодом c2hs.fI - это fromIntegral.Значения во фрагментах get и set относятся к следующей структуре из включенного заголовка, а не к типу Haskell с тем же именем:

struct PlateCTag ;

typedef struct PlateCTag {
  int numX;
  int numY;
  double v1;
  double v2;
} PlateC ;

c2hs преобразует это в следующий простой Haskell:

instance Storable PlateC where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = 24
  peek p =
    PlateC <$> fmap fI ((\ptr -> do {peekByteOff ptr 0 ::IO CInt}) p)
           <*> fmap fI ((\ptr -> do {peekByteOff ptr 4 ::IO CInt}) p)
           <*> fmap realToFrac ((\ptr -> do {peekByteOff ptr 8 ::IO CDouble}) p)
           <*> fmap realToFrac ((\ptr -> do {peekByteOff ptr 16 ::IO CDouble}) p)
  poke p (PlateC xv yv v1v v2v) = do
    (\ptr val -> do {pokeByteOff ptr 0 (val::CInt)}) p (fI xv)
    (\ptr val -> do {pokeByteOff ptr 4 (val::CInt)}) p (fI yv)
    (\ptr val -> do {pokeByteOff ptr 8 (val::CDouble)})   p (realToFrac v1v)
    (\ptr val -> do {pokeByteOff ptr 16 (val::CDouble)})   p (realToFrac v2v)

Смещения, конечно, зависят от архитектуры, поэтому использование препроцессора позволяет писать переносимый код.

Вы используете это, выделяя место для вашего типа данных (new, mallocи т. д.) и poke ввод данных в Ptr (или ForeignPtr).

2) Это реальный макет памяти.

3) Существует штраф за чтение / записьс peek / poke.Если у вас много данных, лучше конвертировать только то, что вам нужно, например, чтение только одного элемента из массива C вместо сортировки всего массива в списке Haskell.

4) Синтаксис зависит от препроцессоратвой выбор. c2hs docs . hsc2hs docs .Смущает то, что hsc2hs использует синтаксис #stuff или #{stuff}, в то время как c2hs использует {#stuff #}.

5) Предложение @ sclv я бы тоже сделал.Написать экземпляр Storable и сохранить указатель на данные.Вы можете либо написать функции C, чтобы выполнить всю работу, и вызывать их через FFI, либо (менее хорошо) написать низкоуровневый Haskell, используя peek и poke, чтобы работать только с теми частями данных, которые вам нужны.Маршаллинг всего этого вперед и назад (то есть вызов peek или poke всей структуры данных) будет дорогостоящим, но если вы только передадите указатели, стоимость будет минимальной.

Вызов импортированных функций черезу FFI есть существенное наказание, если они не отмечены как "небезопасные".Объявление импорта «небезопасным» означает, что функция не должна вызывать обратно в Haskell или неопределенные результаты поведения.Если вы используете параллелизм или параллелизм, это также означает, что все потоки Haskell с одинаковыми возможностями (например, ЦП) будут блокироваться до тех пор, пока не будет возвращен вызов, поэтому он должен вернуться довольно быстро.Если эти условия приемлемы, «небезопасный» вызов выполняется относительно быстро.

В Hackage много пакетов, которые имеют дело с подобными вещами.Я могу порекомендовать hsndfile и hCsound как демонстрирующие хорошую практику с c2hs.Возможно, будет проще, если вы посмотрите на привязку к небольшой библиотеке C, с которой вы знакомы.

7 голосов
/ 21 декабря 2010

Даже если вы можете получить детерминированный макет памяти для строго распакованных структур на Haskell, никаких гарантий нет, и это действительно очень плохая идея.

Если вы готовы жить с преобразованием, есть Storeable: http://www.haskell.org/ghc/docs/6.12.3/html/libraries/base-4.2.0.2/Foreign-Storable.html

Что бы я сделал, это построил структуры C, а затем создал бы функции Haskell, которые работают непосредственно с ними, используя FFI, вместо того, чтобы пытаться создавать для них "эквиваленты" Haskell.

В качестве альтернативы, вы можете решить, что вам нужно только передать выбранный бит информации в C - не все игровое состояние, а всего лишь несколько кусочков информации о том, какие объекты находятся в мире, с вашей реальной информацией о том, какнарисовать их, живущих исключительно в С стороне уравнения.Затем вы выполняете всю логику в Haskell, работая на нативных структурах Haskell, и проецируете в мир C только крошечное подмножество данных, с которым C фактически нужно рендерить.

Редактировать: я должен добавить эти матрицыи другие общие структуры c уже имеют отличные библиотеки / привязки, которые удерживают тяжелую работу на стороне c.

2 голосов
/ 21 декабря 2010

hsc2hs , c → hs и Green Card - все они обеспечивают автоматическую проверку / разбивку или сортировку структуры Haskell®C Я бы порекомендовал использовать их вместо ручного определения размеров и смещений и использования манипуляции с указателем в Haskell, хотя это тоже возможно.

  1. Не настолько, насколько я знаю, если я правильно вас понимаю. Haskell не имеет встроенной обработки внешних статистических структур данных.
  2. 1012 *
  3. 1014 *
  4. Как описывает эта страница, это hsc2hs с некоторой магией Си.
...