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

Dan*_*Dan 5 haskell

我正在编写一个与大型(10-1000 GB)内存映射二进制文件交互的应用程序,基本上保存了一堆相互引用的对象.我想出了一种读取/写入这些数据的机制,这种机制既有效又丑陋(imo).

问:有没有更优雅的方式来实现我的目标?

我有结构化数据类型类一个,其中一个方法,它读取结构成一个Haskell数据类型(DataOp是一个ReaderT围绕IO).

class DBStruct a where
    structRead :: Addr a -> DataOp a
Run Code Online (Sandbox Code Playgroud)

为了使这个更具可读性,我有另一个类型类定义了哪些结构成员去哪里:

class DBStruct st => StructMem structTy valTy name | structTy name -> valTy where
    offset :: structTy -> valTy -> name -> Int64
Run Code Online (Sandbox Code Playgroud)

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

这个问题是它需要大量重复才能使用.对于一个结构,我首先要定义Haskell类型:

data RowBlock = RowBlock {rbNext :: Maybe RowBlock
                         ,rbPrev :: Maybe RowBlock
                         ,rbRows :: [RowTy]
                         }
Run Code Online (Sandbox Code Playgroud)

那么name类型:

data Next = Next
data Prev = Prev
data Count = Count
newtype Row = Row Int64
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

然后是结构读取方法:

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
Run Code Online (Sandbox Code Playgroud)

所以我真正完成的是以更冗长(和更慢)的方式重新实现C结构.如果在保持类型安全的同时更简洁,我会更高兴.当然这是一个常见的问题.

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

  • 沟通内存映射文件并使用Data.Binary,ByteStrings以正常方式写入磁盘.
  • 使用deriving Generic创建通用的读写功能
  • 重载功能参考
  • 使用monadic镜头做一些神奇的事情.

编辑:SSCCE按要求

Lev*_*son 1

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

对于写作:

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

供阅读:

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

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