gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 16:25:58 26 4
gpt4 key购买 nike

首先,我是 Haskell 初学者。

我计划将 Haskell 集成到 C 中以进行实时游戏。Haskell 做逻辑,C 做渲染。为此,我必须在每个刻度(每秒至少 30 次)中相互传递大量结构复杂的数据(游戏状态)。所以传递的数据应该是轻量级的。该状态数据可能位于内存的顺序空间中。 Haskell 和 C 部分都应该可以自由访问状态的每个区域。

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

我正在阅读 Haskell 的 FFI( http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs )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 结构字段的语言结构。

最佳答案

我强烈建议使用预处理器。我喜欢 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 代码。 fIfromIntegral。 get 和 set 片段中的值引用包含的 header 中的以下结构,而不是同名的 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)

偏移量当然是依赖于架构的,所以使用预处理器可以让你编写可移植的代码。

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

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

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

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

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

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

Hackage 上有很多包可以处理这类事情。我可以推荐hsndfilehCsound作为展示 c2hs 的良好实践。不过,如果您查看与您熟悉的小型 C 库的绑定(bind),可能会更容易。

关于c - 在 Haskell 和 C 之间交换结构化数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4502115/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com