如何对自定义数据类型进行内存映射 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
一些解释: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
粘贴到 ByteString
。 unsafeUseAsCString
将 ForeignPtr
强制转换为 Ptr
,然后您可以从中创建一个新的 ForeignPtr
。然后你拿第二个 ForeignPtr
并将它与 S.unsafeFromForeignPtr0
一起使用来创建一个向量。
让两个 ForeignPtr
指向同一个内存是不行的。 GHC 运行时将它们视为两个独立的对象。在对 ByteString
的所有引用都消失后,将调用其 ForeignPtr
的终结器,释放 mmap
并回收底层内存。这使得第二个 ForeignPtr
指向无效区域。
这里的解决方案是使用Data.ByteString.Internal.toForeignPtr
从ByteString
中提取并重新使用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
。所有这些 mmap
和 ForeignPtr
的东西都是危险的,而且比安全正确地做事快不了多少。如果您想要绝对最快的性能而不考虑安全性,请使用 C 语言编程。
设置
我最近实施了基于 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
一些解释: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
粘贴到 ByteString
。 unsafeUseAsCString
将 ForeignPtr
强制转换为 Ptr
,然后您可以从中创建一个新的 ForeignPtr
。然后你拿第二个 ForeignPtr
并将它与 S.unsafeFromForeignPtr0
一起使用来创建一个向量。
让两个 ForeignPtr
指向同一个内存是不行的。 GHC 运行时将它们视为两个独立的对象。在对 ByteString
的所有引用都消失后,将调用其 ForeignPtr
的终结器,释放 mmap
并回收底层内存。这使得第二个 ForeignPtr
指向无效区域。
这里的解决方案是使用Data.ByteString.Internal.toForeignPtr
从ByteString
中提取并重新使用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
。所有这些 mmap
和 ForeignPtr
的东西都是危险的,而且比安全正确地做事快不了多少。如果您想要绝对最快的性能而不考虑安全性,请使用 C 语言编程。