【问题标题】:Is it safe to allocate memory within `unsafeDupablePerformIO`?在 `unsafeDupablePerformIO` 中分配内存是否安全?
【发布时间】:2020-10-16 20:53:31
【问题描述】:

假设我有一个外部函数:

-- | Turns char* of the given size into a char* of size 16.
doSomethingFfi :: Ptr CUChar -> Ptr CUChar -> CSize -> IO ()
doSomethingFfi = undefined

函数是纯函数,所以我想在 Haskell 中将其表示为纯函数:

doSomething :: ByteArray bytes => bytes -> bytes
doSomething bs = unsafePerformIO $
  alloc 16 $ \outPtr ->
  withByteArray bs $ \inPtr ->
    doSomethingFfi outPtr inPtr (fromIntegral $ length bs)

(这里我使用来自memoryalloc。)

我的理解是unsafePerformIOunsafeDupablePerformIO之间的唯一区别是后者中的IO操作可以在不进行任何清理的情况下静默终止。

在我上面的例子中,基本上发生了两个 IO 操作: 1. 内存分配; 2.外呼。我不关心2,因为它是纯粹的,但是我担心内存。

如果计算被静默中断,是否可以保证以这种方式分配的内存不会泄漏?如果外部函数还需要我必须分配/清理的临时存储并且我为此使用了alloca,那么使用unsafeDupablePerformIO 是否仍然安全?

【问题讨论】:

  • 这对我来说听起来很糟糕!您从unsafePerformIO 获得的性能不够好?
  • 我相信 alloca 在这种情况下应该是安全的。它在垃圾收集堆中分配固定内存,因此如果您的IO 被中止,则应该清理它。
  • @dfeuer 我没有对它进行基准测试,所以假设这个问题纯粹是学术问题:)。我只是想知道在这种情况下我应该使用哪个功能并注意到,例如cryptonite uses unsafeDupablePerformIO 并开始怀疑这是否是个好主意。
  • 我认为alloc 也分配了固定的垃圾收集内存。我的问题可以重新表述为“GHC 是否保证 GC 将收集由静默中止 IO 中的分配产生的内存”?
  • 啊,需要注意的是alloc 是一个类方法。我在看implementation for Bytes,你可能在看别的东西。所以它也取决于实例......

标签: haskell memory memory-leaks unsafe-perform-io


【解决方案1】:

大部分是我在 cmets 中解释的,但不完全是:

alloca

由于当前已实施alloca,因此这是安全的。 alloca 是通过调用allocaBytesAligned 来实现的,其定义如下:

allocaBytesAligned :: Int -> Int -> (Ptr a -> IO b) -> IO b
allocaBytesAligned (I# size) (I# align) action = IO $ \ s0 ->
     case newAlignedPinnedByteArray# size align s0 of { (# s1, mbarr# #) ->
     case unsafeFreezeByteArray# mbarr# s1 of { (# s2, barr#  #) ->
     let addr = Ptr (byteArrayContents# barr#) in
     case action addr     of { IO action' ->
     case action' s2      of { (# s3, r #) ->
     case touch# barr# s3 of { s4 ->
     (# s4, r #)
  }}}}}

这会在垃圾收集堆中分配固定内存。如果您的操作提前中止,那么垃圾收集器迟早会回收它分配的内存。

alloc

不一定安全,但实际上可能是安全的。 alloc 是使用类方法allocRet 定义的,不同类型可以不同地实现。

与我在 cmets 中的猜测相反,memory 中定义的实例似乎都很好——它们也分配了固定内存。但是该类没有将此记录为要求,原则上有人可以使用Foreign.Marshall.Alloc.malloc 分配内存,在这种情况下垃圾收集器不会自动处理内存。如果计算提前中止,这样的假设实现将无法确保释放内存。

【讨论】:

  • GHC 是否保证newAlignedPinnedByteArray# 是“原子的”,即线程在malloc 之前或在垃圾收集器“正确注册”之后终止?
  • @kirelagin,primops 不会调用 malloc。我不熟悉固定内存的机制,但作为一项规则,primops 不能 被中断,除非在它们分配的那一刻。我不会担心的。
猜你喜欢
  • 2021-12-30
  • 1970-01-01
  • 2017-11-28
  • 2017-09-16
  • 2015-06-22
  • 2010-09-05
  • 1970-01-01
相关资源
最近更新 更多