Intercambio de datos estructurados entre Haskell y C


Primero, soy un principiante de Haskell.

Estoy planeando integrar Haskell en C para el juego en tiempo real. Haskell hace lógica, C hace renderizado. Para hacer esto, tengo que pasar enormes datos complejos estructurados (estado del juego) de/a uno al otro para cada tick (al menos 30 veces por segundo). Por lo tanto, los datos que pasan deben ser livianos. Estos datos de estado pueden colocarse en espacio secuencial en la memoria. Ambas partes de Haskell y C deben acceder a todas las áreas de los estados libremente.

En el mejor de los casos, el costo de pasar datos puede ser copiar un puntero a una memoria. En el peor de los casos, copiar datos completos con conversión.

Estoy leyendo el FFI de Haskell ( http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs ) El aspecto del código Haskell especificando explícitamente el diseño de la memoria.

Tengo algunas preguntas.

  1. ¿Puede Haskell especificar explícitamente el diseño de la memoria? (para ser emparejado exactamente con la estructura de C)
  2. ¿Es este diseño de memoria real? O cualquier tipo de conversión requerida? (penalización por rendimiento)
  3. Si Q#2 es true, ¿alguna penalización de rendimiento cuando el diseño de memoria especificado explícitamente?
  4. ¿Cuál es la sintaxis #{alignment foo}? ¿Dónde puedo encontrar el documento sobre esto?
  5. Si quiero pasar grandes datos con el mejor rendimiento, ¿cómo debo hacerlo?

* PS Característica de diseño de memoria explícita que dije es solo el atributo [Structulayout] de C#. Que especifica la posición y el tamaño en memoria explícita. http://www.developerfusion.com/article/84519/mastering-structs-in-c /

No estoy seguro de que Haskell tenga un constructo lingüístico coincidente con campos de la estructura C.

Author: Eonil, 2010-12-21

3 answers

Recomiendo encarecidamente el uso de un preprocesador. Me gusta c2hs, pero hsc2hs es muy común porque está incluido con ghc. Greencard parece estar abandonada.

Para responder a sus preguntas:

1) Sí, a través de la definición de la instancia almacenable. El uso de Storable es el único mecanismo seguro para pasar datos a través de la FFI. La instancia almacenable define cómo ordenar los datos entre un tipo Haskell y la memoria raw (ya sea un Haskell Ptr, ForeignPtr, o StablePtr, o un C puntero). He aquí un ejemplo:

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)

Los fragmentos {# ... #} son código c2hs. fI es fromIntegral. Los valores de los fragmentos get y set se refieren a la siguiente estructura de un encabezado incluido, no al tipo Haskell del mismo nombre:

struct PlateCTag ;

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

C2hs convierte esto en el siguiente Haskell simple: {[17]]}

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)

Por supuesto, las compensaciones dependen de la arquitectura, por lo que usar un preprocesador le permite escribir código portable.

Utiliza esto asignando espacio para sus datos tipo (new,malloc, etc.) and poke ing the data into the Ptr (or ForeignPtr).

2) Este es el diseño de memoria real.

3) Hay una pena por leer / escribir con peek/poke. Si tiene muchos datos, es mejor convertir solo lo que necesita, por ejemplo, leer solo un elemento de una matriz C en lugar de ordenar toda la matriz a una lista de Haskell.

4) La sintaxis depende del preprocesador que elija. c2hs docs. hsc2hs docs. Confusamente, hsc2hs usa la sintaxis #stuff o #{stuff}, mientras que c2hs usa {#stuff #}.

5) La sugerencia de @sclv es lo que yo también haría. Escriba una instancia almacenable y mantenga un puntero a los datos. Puede escribir funciones C para hacer todo el trabajo y llamarlas a través del FFI, o (menos bueno) escribir Haskell de bajo nivel usando peek y poke para operar solo en las partes de los datos que necesita. Ordenar todo el asunto de ida y vuelta (es decir, llamar a peek o poke en toda la estructura de datos) será caro, pero si solo pasa punteros alrededor del costo será mínimo.

Llamar a funciones importadas a través de la FFI tiene una penalización significativa a menos que estén marcadas como "inseguras". Declarar una importación "insegura" significa que la función no debe volver a llamar a Haskell o a resultados de comportamiento indefinido. Si está utilizando concurrencia o paralelismo, también significa que todos los subprocesos de Haskell en la misma capacidad (es decir, CPU) se bloquearán hasta que regrese la llamada, por lo que debería regresar bastante pronto. Si esas condiciones son aceptables, una llamada "insegura" es relativamente rápida.

Hay muchos paquetes en Hackage que tratan con este tipo de cosas. Puedo recomendar hsndfile y hCsound como buenas prácticas con c2hs. Probablemente sea más fácil si miras un enlace a una pequeña biblioteca de C con la que estés familiarizado.

 24
Author: John L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-26 11:41:17

Aunque puede obtener un diseño de memoria determinista para estructuras Haskell estrictas sin caja, no hay garantías y es una muy, muy mala idea.

Si estás dispuesto a vivir con la conversión, hay Almacenable: http://www.haskell.org/ghc/docs/6.12.3/html/libraries/base-4.2.0.2/Foreign-Storable.html

Lo que haría es construir las estructuras C, y luego construir funciones Haskell que operan directamente sobre ellas usando el FFI, en lugar de tratar de producir Haskell "equivalentes" a ellos.

Alternativamente, puede decidir que solo necesita pasar un poco de información selecta a la C not no todo el estado del juego, sino solo algunas piezas de información sobre qué objetos están en el mundo, con su información real sobre cómo dibujarlos viviendo únicamente en el lado C de la ecuación. Luego haces toda la lógica en Haskell, operando en estructuras nativas de Haskell, y solo proyectas al mundo C ese pequeño subconjunto de datos que el C realmente necesita renderizar con.

Editar: Debo añadir que las matrices y otras estructuras comunes de c ya tienen excelentes bibliotecas / enlaces que mantienen el trabajo pesado en el lado de c.

 7
Author: sclv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-22 00:50:12

Hsc2hs, c→hs , y Green Card todos proporcionan la estructura automatizada Haskell C C peek/poke o marshalling. Recomendaría su uso sobre la determinación manual de tamaños y compensaciones y el uso de la manipulación de puntero en Haskell, aunque eso también es posible.

  1. No hasta donde yo sé, si te estoy entendiendo correctamente. Haskell no tiene ningún manejo incorporado de estructuras de datos agregados extranjeros.
  2.  
  3.  
  4. Como esa página describe, es hsc2hs con algo de magia C.
 2
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-21 17:49:34