在 unsafeDupablePerformIO 中分配内存是否安全?
Is it safe to allocate memory within `unsafeDupablePerformIO`?
假设我有一个外部函数:
-- | 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)
(这里我使用的是 memory
中的 alloc
。)
我的理解是 unsafePerformIO
和 unsafeDupablePerformIO
的唯一区别是后者的IO动作可以在没有任何清理的情况下静默终止。
在我上面的例子中,基本上发生了两个 IO 操作:1. 内存分配; 2.外呼。我不关心2,因为它是纯的,但是我担心内存。
是否可以保证如果计算被静默中断,以这种方式分配的内存不会泄漏?如果外部函数还需要我必须分配/清理的临时存储并且我为此目的使用了 alloca
,那么使用 unsafeDupablePerformIO
仍然安全吗?
大部分如我在评论中解释的那样,但不完全是:
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 #)
}}}}}
这会在 garbage-collected 堆中分配固定内存。如果您的操作提前中止,那么垃圾收集器迟早会回收它分配的内存。
alloc
这不一定安全,但实际上可能是安全的。 alloc
是使用 class 方法定义的,allocRet
,不同的类型可以不同地实现。
与我在评论中的猜测相反,memory
中定义的所有实例 似乎 都很好——它们也分配固定内存。但是 class 没有将此记录为要求,原则上有人可以使用 Foreign.Marshall.Alloc.malloc
分配内存,在这种情况下垃圾收集器不会自动处理内存。如果计算提前中止,这种假设的实现将无法确保释放内存。
假设我有一个外部函数:
-- | 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)
(这里我使用的是 memory
中的 alloc
。)
我的理解是 unsafePerformIO
和 unsafeDupablePerformIO
的唯一区别是后者的IO动作可以在没有任何清理的情况下静默终止。
在我上面的例子中,基本上发生了两个 IO 操作:1. 内存分配; 2.外呼。我不关心2,因为它是纯的,但是我担心内存。
是否可以保证如果计算被静默中断,以这种方式分配的内存不会泄漏?如果外部函数还需要我必须分配/清理的临时存储并且我为此目的使用了 alloca
,那么使用 unsafeDupablePerformIO
仍然安全吗?
大部分如我在评论中解释的那样,但不完全是:
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 #)
}}}}}
这会在 garbage-collected 堆中分配固定内存。如果您的操作提前中止,那么垃圾收集器迟早会回收它分配的内存。
alloc
这不一定安全,但实际上可能是安全的。 alloc
是使用 class 方法定义的,allocRet
,不同的类型可以不同地实现。
与我在评论中的猜测相反,memory
中定义的所有实例 似乎 都很好——它们也分配固定内存。但是 class 没有将此记录为要求,原则上有人可以使用 Foreign.Marshall.Alloc.malloc
分配内存,在这种情况下垃圾收集器不会自动处理内存。如果计算提前中止,这种假设的实现将无法确保释放内存。