在 Haskell 和 C 之间交换结构化数据

发布于 2024-10-09 02:13:28 字数 1015 浏览 3 评论 0原文

首先,我是 Haskell 初学者。

我计划将 Haskell 集成到 C 中以实现实时游戏。 Haskell 负责逻辑,C 负责渲染。为此,我必须在每次更新之间相互传递大量复杂的结构化数据(游戏状态)(每秒至少 30 次)。所以传递的数据应该是轻量级的。该状态数据可以放置在存储器上的连续空间上。 Haskell 和 C 部分都应该可以自由访问各州的每个区域。

在最好的情况下,传递数据的成本可能是将指针复制到内存。在最坏的情况下,复制整个数据并进行转换。

我正在阅读 Haskell 的 FFI(http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs< /a>) Haskell 代码看起来明确指定了内存布局。

我有几个问题。

  1. Haskell 可以显式指定内存布局吗? (与C结构体完全匹配)
  2. 这是真正的内存布局吗?或者需要任何类型的转换? (性能损失)
  3. 如果 Q#2 为真,那么显式指定内存布局时是否会产生性能损失?
  4. #{alignment foo} 的语法是什么?我在哪里可以找到有关此内容的文档?
  5. 如果我想以最佳性能传递大量数据,我应该怎么做?

*附注 我所说的显式内存布局功能只是 C# 的 [StructLayout] 属性。这是明确指定内存中的位置和大小。 http://www.developerfusion.com/article/84519/mastering- structs-in-c/

我不确定 Haskell 是否有与 C 结构体字段相匹配的语言结构。

First, I'm a Haskell beginner.

I'm planning integrating Haskell into C for realtime game.
Haskell does logic, C does rendering. To do this, I have to pass huge complexly structured data (game state) from/to each other for each tick (at least 30 times per second). So the passing data should be lightweight. This state data may laid on sequential space on memory. Both of Haskell and C parts should access every area of the states freely.

In best case, the cost of passing data can be copying a pointer to a memory. In worst case, copying whole data with conversion.

I'm reading Haskell's FFI(http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs)
The Haskell code look specifying memory layout explicitly.

I have a few questions.

  1. Can Haskell specify memory layout explicitly? (to be matched exactly with C struct)
  2. Is this real memory layout? Or any kind of conversion required? (performance penalty)
  3. If Q#2 is true, Any performance penalty when the memory layout specified explicitly?
  4. What's the syntax #{alignment foo}? Where can I find the document about this?
  5. If I want to pass huge data with best performance, how should I do that?

*PS
Explicit memory layout feature which I said is just C#'s [StructLayout] attribute. Which is specifying in-memory position and size explicitly.
http://www.developerfusion.com/article/84519/mastering-structs-in-c/

I'm not sure Haskell has matching linguistic construct matching with fields of C struct.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

友谊不毕业 2024-10-16 02:13:28

我强烈建议使用预处理器。我喜欢 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 <
gt; 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 代码。 fIfromIntegral。 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 <
gt; 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)

偏移量当然取决于体系结构,因此使用预定义处理器允许您编写可移植的代码。

您可以通过为数据类型(newmalloc 等)分配空间并将数据poke放入 Ptr(或foreignPtr)来使用它。 )。

2)这才是真正的内存布局。

3) 使用peek/poke进行读/写会受到惩罚。如果您有大量数据,最好只转换您需要的数据,例如仅从 C 数组中读取一个元素,而不是将整个数组编组到 Haskell 列表。

4) 语法取决于您选择的预处理器。 c2hs 文档hsc2hs 文档。令人困惑的是,hsc2​​hs 使用语法 #stuff#{stuff},而 c2hs 使用 {#stuff #}

5)@sclv的建议也是我会做的。编写一个 Storable 实例并保留指向数据的指针。您可以编写 C 函数来完成所有工作并通过 FFI 调用它们,或者(不太好)使用 peek 和 poke 编写低级 Haskell 来仅对您需要的数据部分进行操作。来回编组整个事情(即在整个数据结构上调用peekpoke)将是昂贵的,但如果您只传递指针,则成本将是最小的。

通过 FFI 调用导入函数会受到严重惩罚,除非它们被标记为“不安全”。声明导入“不安全”意味着该函数不应回调到 Haskell 或未定义的行为结果。如果您使用并发或并行性,这也意味着相同功能(即 CPU)上的所有 Haskell 线程将阻塞,直到调用返回,因此它应该相当快地返回。如果这些条件可以接受,“不安全”调用相对较快。

Hackage 上有很多处理此类事情的软件包。我可以推荐 hsndfilehCsound 展示了 c2hs 的良好实践。如果您查看与您熟悉的小型 C 库的绑定,可能会更容易。

I would strongly recommend using a preprocessor. I like c2hs, but hsc2hs is very common because it's included with ghc. Greencard appears to be abandoned.

To answer your questions:

1) Yes, through the definition of the Storable instance. Using Storable is the only safe mechanism to pass data through the FFI. The Storable instance defines how to marshal data between a Haskell type and raw memory (either a Haskell Ptr, ForeignPtr, or StablePtr, or a C pointer). Here's an example:

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 <
gt; 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)

The {# ... #} fragments are c2hs code. fI is fromIntegral. The values in the get and set fragments refer to the following struct from an included header, not the Haskell type of the same name:

struct PlateCTag ;

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

c2hs converts this to the following plain Haskell:

instance Storable PlateC where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = 24
  peek p =
    PlateC <
gt; 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)

The offsets are of course architecture-dependent, so using a pre-processer allows you to write portable code.

You use this by allocating space for your data type (new,malloc, etc.) and pokeing the data into the Ptr (or ForeignPtr).

2) This is the real memory layout.

3) There is a penalty for reading/writing with peek/poke. If you have a lot of data, it's better to convert only what you need, e.g. reading just one element from a C array instead of marshalling the entire array to a Haskell list.

4) Syntax depends upon the preprocessor you choose. c2hs docs. hsc2hs docs. Confusingly, hsc2hs uses the syntax #stuff or #{stuff}, while c2hs uses {#stuff #}.

5) @sclv's suggestion is what I would do as well. Write a Storable instance and keep a pointer to the data. You can either write C functions to do all the work and call them through the FFI, or (less good) write low-level Haskell using peek and poke to operate on just the parts of the data you need. Marshalling the whole thing back and forth (i.e. calling peek or poke on the entire data structure) will be expensive, but if you only pass pointers around the cost will be minimal.

Calling imported functions through the FFI has a significant penalty unless they're marked "unsafe". Declaring an import "unsafe" means that the function should not call back into Haskell or undefined behavior results. If you're using concurrency or parallelism, it also means that all Haskell threads on the same capability (i.e. CPU) will block until the call returns, so it should return fairly quickly. If those conditions are acceptable an "unsafe" call is relatively fast.

There are a lot of packages on Hackage that deal with this sort of thing. I can recommend hsndfile and hCsound as exhibiting good practice with c2hs. It's probably easier if you look at a binding to a small C library you're familiar with though.

一梦浮鱼 2024-10-16 02:13:28

尽管您可以获得严格的未装箱 Haskell 结构的确定性内存布局,但没有任何保证,这确实是一个非常糟糕的主意。

如果您愿意接受转换,可以使用 Storeable:http://www.haskell.org/ghc/docs/6.12.3/html/libraries/base-4.2.0.2/Foreign-Storable.html

我会做什么构造 C 结构,然后构造使用 FFI 直接对其进行操作的 Haskell 函数,而不是尝试生成与它们“等价”的 Haskell 函数。

或者,您可以决定只需要向 C 传递选定的信息 - 不是整个游戏状态,而只是关于对象在世界上的位置的一些信息,以及关于如何进行操作的实际信息。画出他们只生活在等式的C端。然后,您在 Haskell 中执行所有逻辑,对本机 Haskell 结构进行操作,并且仅将 C 实际需要渲染的数据的微小子集投影到 C 世界。

编辑:我应该补充一点,矩阵和其他常见的 c 结构已经拥有出色的库/绑定,可以将繁重的工作保持在 c 端。

Even though you can get deterministic memory layout for strict unboxed Haskell structures, there are no guarantees and it is a really really bad idea.

If you're willing to live with conversion, there's Storeable: http://www.haskell.org/ghc/docs/6.12.3/html/libraries/base-4.2.0.2/Foreign-Storable.html

What I'd do is construct the C structures, and then construct Haskell functions that operate directly on them using the FFI, rather than trying to produce Haskell "equivalents" to them.

Alternately, you can decide that you only need to pass a select bit of information to the C -- not the whole game state, but just a few pieces of information about what objects are where in the world, with your actual information on how to draw them living solely in the C side of the equation. Then you do all the logic in Haskell, operating on native Haskell structures, and only project out to the C world that tiny subset of data which the C actually needs to render with.

Edit: I should add that matrices and other common c structures already have excellent libraries/bindings that keep the heavy lifting on the c side.

苦行僧 2024-10-16 02:13:28

hsc2hsc→hsGreen Card 都提供自动化的 Haskell⇆C 结构 peek/poke 或编组。我建议使用它们,而不是手动确定大小和偏移量以及在 Haskell 中使用指针操作,尽管这也是可能的。

  1. 据我所知,如果我没理解错的话。 Haskell 没有任何对外部聚合数据结构的内置处理。
  2.  
  3.  
  4. 正如该页面所描述的,它是带有一些 C 魔法的 hsc2hs

hsc2hs, c→hs, and Green Card all provide automated Haskell⇆C structure peek/poke or marshalling. I would recommend their use over manually determining sizes and offsets and using pointer manipulation in Haskell, although that's possible too.

  1. Not as far as I know, if I'm understanding you correctly. Haskell doesn't have any built-in handling of foreign aggregate data structures.
  2.  
  3.  
  4. As that page describes, it's hsc2hs with some C magic.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文