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

时间:2013-08-07 15:04:45

标签: haskell

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

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

我有一个结构化数据的类型类,有一个方法可以将结构读入Haskell数据类型(DataOpReaderT IO周围。

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

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

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

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

然后name类型:

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
  • 使用monadic镜片做一些神奇的事。

编辑:SSCCE as requested

1 个答案:

答案 0 :(得分:1)

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

写作:

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

阅读:

您可以使用Data.ByteString.Internal的fromForiegnPtr将Ptr转换为字节字符串。这基本上是mmap库的功能,但你可以一次做一个记录而不是整个区域。在内存上有一个ByteString视图后,可以使用Data.Binary将其解压缩。

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