有没有办法确保某些特定类型的对象(基本上ForeignPtr)非常积极地进行垃圾收集?我有这样一种简单的类型:
data SomePtr = SomePtr { ptr :: ForeignPtr CUChar, size :: CSize }
alloc :: CSize -> IO SomePtr
free :: SomePtr -> IO ()
free = finalizeForeignPtr . ptr
Run Code Online (Sandbox Code Playgroud)
我认为标准理论是引用计数(我自己也会这样做,比方说,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 ()
Run Code Online (Sandbox Code Playgroud)
#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;
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
如果我们想要独立于 GHC 的垃圾收集,我们需要引入某种确定性,从而引入显式的释放。分配通常是 type 的东西IO a,以及相应的释放类型a -> IO ()(就像你的例子一样)。
现在,如果我们有以下功能怎么办?
allocate :: IO a -> (a -> IO ()) -> Alloc a
runAlloc :: Alloc a -> IO a
Run Code Online (Sandbox Code Playgroud)
autoAllocate应该同时进行分配和释放,并给出新(表面)monad 中分配的结果Alloc,并runAlloc运行所有操作和释放。除了结尾之外,您的示例不会有太大变化:
allocateFPtr size = autoAllocate (newFPtr size) freeFPtr
main :: IO ()
main = forM_ [1 .. 5] $ runAlloc . const work
where
work = do x <- allocateFPtr 1024
return ()
Run Code Online (Sandbox Code Playgroud)
现在,allocate、runAlloc和Alloc已经存在于resourcetas allocate、runResourceTand中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 ()
Run Code Online (Sandbox Code Playgroud)
结果:
法洛克(1024, 0x1e04014, 0); 1 结果:0 ffree(0x6abc60) 法洛克(1024, 0x1e04020, 0); 2 结果:0 ffree(0x6abc60) 法洛克(1024, 0x1e0402c, 0); 3 结果:0 ffree(0x6abc60) 法洛克(1024, 0x1e04038, 0); 4 结果:0 ffree(0x6abc60) 法洛克(1024, 0x1e04044, 0); 5 结果:0 ffree(0x6abc60)
但你说你的一些指针实际上应该寿命更长。这也不是问题,因为allocate实际上返回m (ReleaseKey, a),并且ReleaseKey可用于在runResourceT(使用release)之前释放内存或删除自动释放机制(使用unprotect,它返回释放操作)。
所以,总而言之,我想你的场景可以很好地处理ResourceT。毕竟,它的概要是“稀缺资源的确定性分配和释放”。