haskell - 使用 Haskell 存储大型结构化二进制数据

标签 haskell

我正在编写一个与大型(10-1000 GB)内存映射二进制文件交互的应用程序,该文件本质上保存着一堆相互引用的对象。我想出了一种读/写这些数据的机制,它很有效,但丑陋且冗长(imo)。

问:有没有更优雅的方式来实现我所做的事情?

我有一个用于结构化数据的类型类,其中有一种方法可以将结构读入 Haskell 数据类型(DataOp 是围绕 IOReaderT )。

class DBStruct a where
    structRead :: Addr a -> DataOp a

为了使其更具可读性,我有另一个类型类,它定义了结构成员的去向:

class DBStruct st => StructMem structTy valTy name | structTy name -> valTy where
    offset :: structTy -> valTy -> name -> Int64

我有一些辅助函数,它们使用偏移方法来读取/写入结构元素、从存储的引用中读取结构以及延迟延迟结构读取(以允许延迟读取整个文件)。

这样做的问题是它涉及大量的重复使用。对于一个结构,我首先必须定义 Haskell 类型:

data RowBlock = RowBlock {rbNext :: Maybe RowBlock
                         ,rbPrev :: Maybe RowBlock
                         ,rbRows :: [RowTy]
                         }

然后名称类型:

data Next = Next
data Prev = Prev
data Count = Count
newtype Row = Row Int64

然后是每个结构成员的实例:

instance StructMem RowBlock (Maybe (Addr RowBlock)) Next where offset _ _ _ = 0
instance StructMem RowBlock (Maybe (Addr RowBlock)) Prev where offset _ _ _ = 8
instance StructMem RowBlock Int64 Count where offset _ _ _ = 16
instance StructMem RowBlock RowTy Row where offset _ _ (Row n) = 24 + n * 8

然后是结构体读取方法:

instance DBStruct RowBlock where
    structRead a = do
        n <- elemMaybePtr a Next
        p <- elemMaybePtr a Prev
        c <- elemRead a Count
        rs <- mapM (elemRead a . Row) [0 .. c-1]
        return $ RowBlock n p rs

所以我真正完成的只是以更详细(且更慢)的方式重新实现 C 结构。如果这在保持类型安全的同时更加简洁,我会更高兴。这肯定是一个常见的问题。

我能想到的一些可能的替代方案是:

  • 放弃内存映射文件并使用 Data.Binary,以正常方式将 ByteStrings 写入磁盘。
  • 使用deriving Generic创建通用读写函数
  • Overload Functional References
  • 用单透镜做一些神奇的事情。

编辑:SSCCE as requested

最佳答案

您可以尝试将 Data.Binary 与您的 Ptr 一起使用。

写作:

使用 Data.Binary 构建 ByteString。 ByteString 是一个元组 (ForeignPtr Word8, Int, Int),它保存数据存储的地址、偏移量和长度。您可以使用 Data.ByteString.Internal 包获取 toForeignPtr,它将为您解压元组。 foreign.ForeignPtr 提供了 withForeignPtr,它采用一个通过指针执行 IO 操作的函数。在那里你可以将字节串存储memcpy(Data.ByteString.Internal中也提供了绑定(bind))到从mmap获得的mmaped Ptr。

供阅读:

您可以使用 Data.ByteString.Internal 的 fromForiegnPtr 将 Ptr 转换为字节串。这基本上就是 mmap 库所做的事情,但是您可以一次执行一条记录,而不是对整个区域进行记录。一旦你有了内存上的 ByteString View ,你就可以用 Data.Binary 来解压它。

另一个选择是利用 ByteString 在 Data.Vector.Storable.ByteString 中有一个替代实现,它允许您使用现在使用的 Storable 接口(interface)将它们读/写到映射的 Ptr。接口(interface)和基本类型与 Data.ByteString 是同构的,但它有 Storable 实例。

关于haskell - 使用 Haskell 存储大型结构化二进制数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18106958/

相关文章:

haskell - 获取 Data.ByteString.Builder 的长度

haskell - 约束包是如何工作的?

haskell - 如何在 Haskell 中将人类语言单元编写为后缀,例如 `3 seconds` ?

c++ - 为什么 Haskell 对于简单的斐波那契比 C++ 更快

haskell - Haskell中的这些方括号是什么?

haskell - "data"和 "type"关键字有什么区别?

haskell :|在类声明中

haskell - 将 Haskell 用于大型实时系统 : how (if? )?

haskell - 避免 Haskell 中 monad 内的模式匹配

haskell - "closed under composition"的含义