在haskell在不再引用它们之后立即完成外部指针

Kir*_*ill 8 haskell

有没有办法确保某些特定类型的对象(基本上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)

C++

#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)

Zet*_*eta 4

如果我们想要独立于 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)

现在,allocaterunAllocAlloc已经存在于resourcetas allocaterunResourceTand中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。毕竟,它的概要是“稀缺资源的确定性分配和释放”。