Посторонние данные и сборка мусора
Когда я выделяю некоторые данные через FFI и связываю с ними финализатор, я получаю ForeignPtr
в Хаскеле. Когда этот указатель становится не связанным, GC собирает указатель, что приводит к запуску финализатора. Но сбор происходит только тогда, когда GC запущен, и "разыменование" не заставляет GC работать. То есть может лежать много указателей, но поскольку сами указатели не занимают много памяти, RTS просто не видит причины для запуска GC, потому что, согласно моим исследованиям, размер внешних данных не отслеживается RTS. Это правильно?
Как мне сообщить "когда этот указатель становится не связанным, немедленно собрать его" в RTS? Есть ли флаги, позволяющие контролировать, когда запускать GC? Является ли это проблемой для реальной программы (поскольку любая реальная программа всегда имеет достаточно явного мусора, чтобы стимулировать GC)?
2 ответа
RTS не имеет представления, не ссылается ли какая-либо часть данных до запуска GC. GHC не имеет счетчика ссылок GC, который позволял бы немедленно предпринимать действия с мусором. Вы можете попробовать подсчитать ссылки самостоятельно или использовать GC вручную из System.Mem
,
Иностранные ассигнования не отслеживаются в Haskell-land. Если вам нужен больший контроль, но нет пользовательского GC или подсчета ссылок, вы можете использовать, например, Foreign.Marhsal.Array
для ручного / ограниченного выделения и освобождения.
Другой вариант - использовать закрепленное распределение в GHC RTS. Это дает вам память, которая не перемещается GC. Ссылки на закрепленные данные могут передаваться во внешний код без накладных расходов, но закрепленные данные отслеживаются, могут иметь значение GC-d и запускать GC так же, как это делают обычные данные кучи. Вот один API для закрепленных данных. Другой выбор просто ByteString
, Возможным недостатком закрепленных данных является фрагментация памяти и более медленное выделение, но это также относится к (любому) внешнему выделению, которое возвращает стабильные указатели.
Понимание того, когда указатель становится не связанным, не является тривиальным. Насколько я знаю, нет способа выполнить то, что вы запрашиваете, а именно сообщить GC, что указатель больше не доступен. В лучшем случае можно запустить цикл GC, но нет жестких гарантий.
Из вашего описания вы, вероятно, предпочтете механизм подсчета ссылок вместо сборки мусора. Однако, особенно в сложном чистом коде, трудно определить точки, где счетчик должен быть увеличен или уменьшен: было бы легче в монаде на основе состояния или ввода-вывода, где такие побочные эффекты должным образом упорядочены по сравнению с остальной частью вычисления.
Если вам действительно не нужен подсчет ссылок за пределами "одного", то довольно распространенная идиома - это использование with
-стиль для обработки выделения и освобождения. Это может быть немного сложнее, чтобы справиться правильно.
Например, тривиальная реализация может быть
-- very simplified code
withMyResource :: (ResourcePtr -> IO r) -> IO r
withMyResource action = do
p <- allocResourcePtr
result <- action p
deallocResourcePtr p
return result
Это может быть использовано как
withResource $ \ptr -> do
use ptr
Обратите внимание, что это не совсем безопасно, так как можно вернуть указатель, заставив его работать после освобождения
ptr <- withResource return
use ptr -- dangerous!
Правильно обработанная указатель должна работать как ST
монада и ее теги STRef
s, которые спроектированы так, чтобы указатели не выходили за пределы их предполагаемой области видимости (как сделано выше). Это использует типы ранга 2, но эффективно.
Тем не менее, можно жить с наивным with
рутина, и просто будьте осторожны, чтобы не дать указателям убежать.
Другая проблема небезопасности вызвана action
возможность бросать исключения. (Это можно сделать с помощью bracket
-подобные процедуры из библиотеки).