Подтвердить что ты не робот

Обмен структурированными данными между Haskell и C

Во-первых, я начинающий Haskell.

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

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

Я читаю Haskell FFI (http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs) Код Haskell явно указывает макет памяти.

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

  • Может ли Haskell явно указывать макет памяти? (для соответствия точно с C struct)
  • Это реальная память? Или требуется какое-либо преобразование? (штраф за исполнение)
  • Если Q # 2 истинно, любое ограничение производительности при явно указанном макете памяти?
  • Какой синтаксис #{alignment foo}? Где я могу найти документ об этом?
  • Если я хочу передавать огромные данные с максимальной производительностью, как мне это сделать?

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

Я не уверен, что у Haskell есть соответствующая лингвистическая конструкция, соответствующая полям C struct.

4b9b3361

Ответ 1

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

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

1) Да, через определение экземпляра Storable. Использование 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 или undefined. Если вы используете concurrency или parallelism, это также означает, что все потоки Haskell с одинаковой возможностью (т.е. CPU) будут блокироваться до тех пор, пока вызов не вернется, поэтому он должен вернуться довольно быстро. Если эти условия приемлемы, "небезопасный" вызов относительно быстр.

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

Ответ 2

Несмотря на то, что вы можете получить детерминированный макет памяти для строгих unboxed структур 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.

Ответ 3

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

  • Насколько я знаю, если я правильно понимаю вас. Haskell не имеет встроенной обработки внешних агрегатных структур данных.
  • Как описывает эта страница, она hsc2hs с некоторой магией C.