如何对自定义数据类型进行内存映射 IO?

How to do memory mapped IO on custom data types?

设置

我最近实施了基于 mmap 的文件读取并直接 运行 进入 st运行ge 行为。相关代码为:

-- | map whole aedat file into memory and return it as a vector of events
-- TODO what are the finalizing semantics of this?
mmapAERData :: S.Storable a => FilePath -> IO (S.Vector (AER.Event a))
mmapAERData name = do
    -- mmap file into memory and find the offset behind the header
    bs <- dropHeader <$> mmapFileByteString name Nothing
    -- some conversion is necessary to get the 'ForeignPtr' from
    -- a 'ByteString'
    B.unsafeUseAsCString bs $ \ptr -> do
      fptr <- newForeignPtr_ ptr
      let count = B.length bs `div` 8 -- sizeof one event
      return $ S.unsafeFromForeignPtr0 (castForeignPtr fptr) count

→ code in context

一些解释:AEDat 格式基本上是一个包含两个 Word32 的长列表。一个编码地址,另一个编码时间戳。在此之前,我将几行 header 文本放入 dropHeader 函数中。如果绝对必要,我可以直接在 ForeignPtr 上执行此操作,但我更喜欢使用适用于 ByteStrings 的通用函数。

可以找到 Storable 个实例 here and here。我不确定这里的 alignment,但我怀疑 8 的对齐应该是正确的。

问题

读取数据工作得很好,但一段时间后内存似乎以某种方式损坏:

>>> es <- DVS.mmapDVSData "dataset.aedat" 
>>> es S.! 1000
Event {address = Address {polarity = D, posX = 6, posY = 50}, timestamp = 74.771407s}
>>> :type es
es :: S.Vector (DVS.Event DVS.Address)
>>> _ <- evaluate (V.convert es :: V.Vector (DVS.Event DVS.Address))
>>> es S.! 1000
Event {address = Address {polarity = D, posX = 0, posY = 44}, timestamp = 0s}

显然访问 es 的所有元素以某种方式破坏了我的记忆。或者垃圾收集器回收它?无论哪种方式,这都是 st运行ge。我该怎么办?

mmapFileByteString 执行 mmap,创建 ForeignPtr,并将 ForeignPtr 粘贴到 ByteStringunsafeUseAsCStringForeignPtr 强制转换为 Ptr,然后您可以从中创建一个新的 ForeignPtr。然后你拿第二个 ForeignPtr 并将它与 S.unsafeFromForeignPtr0 一起使用来创建一个向量。

让两个 ForeignPtr 指向同一个内存是不行的。 GHC 运行时将它们视为两个独立的对象。在对 ByteString 的所有引用都消失后,将调用其 ForeignPtr 的终结器,释放 mmap 并回收底层内存。这使得第二个 ForeignPtr 指向无效区域。

这里的解决方案是使用Data.ByteString.Internal.toForeignPtrByteString中提取并重新使用ForeignPtr。将 unsafeUseAsCString 块替换为:

let (fptr,offset,len) = Data.ByteString.Internal.toForeignPtr bs
-- it might be worthwhile to assert that offset == 0
let count = len `div` 8
return $ S.unsafeFromForeignPtr0 (castForeignPtr fptr) count

恕我直言,这里真正的解决方案根本不是 fiddle 所有这些东西。只需按常规将文件读入 ByteString,从中提取 8 字节的子字符串并手动将它们转换为 Event。所有这些 mmapForeignPtr 的东西都是危险的,而且比安全正确地做事快不了多少。如果您想要绝对最快的性能而不考虑安全性,请使用 C 语言编程。