有没有办法确保某些特定类型的对象(基本上ForeignPtr
)被非常积极地垃圾收集?我有这样一种简单的类型:
data SomePtr = SomePtr { ptr :: ForeignPtr CUChar, size :: CSize }
alloc :: CSize -> IO SomePtr
free :: SomePtr -> IO ()
free = finalizeForeignPtr . ptr
我认为标准理论是引用计数(我自己也会这样做,比方说,C ++)比ghc使用的GC慢,这就是为什么它没有使用它。但对我来说问题是,当使用外部分配的对象(如GPU内存)时,最终被释放的承诺是不够的。内存非常稀缺,据我所知,ForeignPtr
终结器实际上并没有被调用。我希望尽快释放内存,所以我最终自己打电话给finalizeForeignPtr
。
是否有某种方法可以告诉ghc在破坏某些特定类型的对象时非常积极?
或者我的方式错了?
以下示例代码用于说明我的意思:
{-# LANGUAGE RecordWildCards #-}
import Foreign.ForeignPtr.Safe
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Storable
import Control.Monad
import Foreign.C.Types
import Text.Printf
data FPtr = FPtr { fptr :: ForeignPtr CUChar, size :: CSize }
foreign import ccall "falloc" falloc :: CSize -> Ptr (Ptr CUChar) -> IO CInt
foreign import ccall "&ffree" ffree :: FunPtr (Ptr CUChar -> IO ())
newFPtr :: CSize -> IO FPtr
newFPtr size =
do alloca $ \ptr -> do
result <- falloc size ptr
printf "Result: %d\n" (fromIntegral result :: Int)
fptr <- newForeignPtr ffree =<< peek ptr
return FPtr{..}
freeFPtr :: FPtr -> IO ()
freeFPtr = finalizeForeignPtr . fptr
main :: IO ()
main = forM_ [1 .. 5] $ const work
where
work = do x <- newFPtr 1024
-- freeFPtr x
return ()
#include <cstdio>
using namespace std;
extern "C" {
int falloc(size_t size, unsigned char** ptr);
void ffree(unsigned char* ptr);
}
int some_counter = 0;
int falloc(size_t size, unsigned char** ptr) {
some_counter++;
printf("falloc(%lu, %#lx, %#lx); %d\n",
size, (unsigned long)ptr, (unsigned long)*ptr, some_counter);
*ptr = new unsigned char[size];
return 0;
}
void ffree(unsigned char* ptr) {
printf("ffree(%#lx)\n", (unsigned long)ptr);
delete[] ptr;
}
falloc(1024, 0x100606010, 0); 1
Result: 0
falloc(1024, 0x100606028, 0); 2
Result: 0
falloc(1024, 0x100606040, 0); 3
Result: 0
falloc(1024, 0x100606058, 0); 4
Result: 0
falloc(1024, 0x100606070, 0); 5
Result: 0
ffree(0x101026400)
ffree(0x101027800)
ffree(0x101027c00)
ffree(0x101028000)
ffree(0x101028400)
falloc(1024, 0x100606010, 0); 1
Result: 0
ffree(0x101026400)
falloc(1024, 0x100606028, 0); 2
Result: 0
ffree(0x100802200)
falloc(1024, 0x100606040, 0); 3
Result: 0
ffree(0x100802200)
falloc(1024, 0x100606058, 0); 4
Result: 0
ffree(0x100802200)
falloc(1024, 0x100606070, 0); 5
Result: 0
ffree(0x100802200)
答案 0 :(得分:2)
你正在以错误的方式考虑这个问题 for Haskell。
在C ++中,RAII用于确保及时释放资源。由于C ++缺少finally
构造,因此没有其他方法可以确保在存在异常的情况下释放资源。此外,由于C ++缺少垃圾收集器,引用计数和RAII是当天的顺序。
然而,在Haskell(以及其他垃圾收集语言)中,情况有所不同。一个人不依赖于即时运行的终结器。实际上,根本不应该依赖于终结器运行,因为如果可用内存量足够高,它们可能会被延迟一段任意时间 - 并且如果永远不会被执行程序在终结器(甚至垃圾收集器)有机会运行之前终止,因为对象无法访问。
相反,使用显式资源释放。这似乎很糟糕,但并非如此。出于记忆安全的原因,人们应该把对象放在&#34; zombie&#34;状态,以便任何进一步尝试使用该对象抛出异常(因为它们是错误)。
或者,如果资源是在进程退出时自动释放的,那么可以依赖终结器 - 但请注意,它们可能不会被及时调用(如您所述),因此显式performGC
如果资源耗尽,可能需要。我怀疑不知道真正稀缺资源的生命何时结束(至少保守)可能是代码味道,即使在C ++中 - 这意味着消耗的资源量没有上限。
答案 1 :(得分:2)
如果我们想要独立于GHC的垃圾收集,我们需要引入某种决定论,因此需要明确的解除分配。分配通常是类型为IO a
的类型,以及类型a -> IO ()
的相应解除分配(就像您的示例一样)。
现在,如果我们有以下功能怎么办?
allocate :: IO a -> (a -> IO ()) -> Alloc a
runAlloc :: Alloc a -> IO a
autoAllocate
应该同时进行分配和释放,并在新(表面)Alloc
monad中提供分配结果,runAlloc
运行所有操作和解除分配。你的例子不会改变那么多,除了结尾:
allocateFPtr size = autoAllocate (newFPtr size) freeFPtr
main :: IO ()
main = forM_ [1 .. 5] $ runAlloc . const work
where
work = do x <- allocateFPtr 1024
return ()
现在,allocate
,runAlloc
和Alloc
已作为resourcet
,allocate
和runResourceT
存在于ResourceT
中,并且实际代码如下所示:
allocateFPtr size = fmap snd $ allocate (newFPtr size) freeFPtr
main :: IO ()
main = forM_ [1 .. 5] $ runResourceT . const work
where
work = do x <- allocateFPtr 1024
return ()
结果:
falloc(1024, 0x1e04014, 0); 1 Result: 0 ffree(0x6abc60) falloc(1024, 0x1e04020, 0); 2 Result: 0 ffree(0x6abc60) falloc(1024, 0x1e0402c, 0); 3 Result: 0 ffree(0x6abc60) falloc(1024, 0x1e04038, 0); 4 Result: 0 ffree(0x6abc60) falloc(1024, 0x1e04044, 0); 5 Result: 0 ffree(0x6abc60)
但你说你的一些指针实际上应该活得更久。这也不是问题,因为allocate
实际上返回m (ReleaseKey, a)
,而ReleaseKey
可用于在runResourceT
之前释放内存(使用{{3} }}或删除自动释放机制(使用release
,它返回释放操作)。
总而言之,我猜你的情景可以通过ResourceT
很好地处理。毕竟,它的概要是&#34;确定性分配和释放稀缺资源&#34;。
答案 2 :(得分:2)
在非常有限的情况下,您只关心释放一些可以在haskell堆上存活的内存,可以使用特殊的边缘情况。
mallocForeignPtr
将内存分配为haskell堆上的固定可变字节数组,因此当ForeignPtr
(和可变字节数组)得到GC时,内存会自动回收到没有终结器调用。
这比添加手动挂钩来调用与系统free
相对应的某些malloc
要便宜得多,但仅限于您可以忍受限制的有限情况。
但是,如果您依赖于释放其他资源(例如通过文件句柄对象,或GPU或其他内容中的内存或资源ID),您仍然需要进行管理。
一般情况下,不要依赖GC为您释放宝贵的外部资源,除非作为一种道歉&#34;在例如异常等期间通过泄露的东西。您通常的控制流程仍应释放您使用的外部资源。