Ответ 1
Это интересная идея, и хотя она довольно тонкая, я считаю, что вы можете с ней справиться.
Как вы заметили, сборщик мусора должен отслеживать, какие массивы изменяемы, поскольку они могут содержать ссылки на объекты, находящиеся в младших поколениях, чем у самого массива. Объекты в этом списке будут служить корнями при сборе младших поколений.
Чтобы понять, почему, представьте, что вы выделяете (в питомнике, поколение 0) изменяемый массив с некоторыми указателями на другие вновь выделенные объекты кучи. При сборке мусора, как массив, так и его содержимое будут перемещены в поколение 1, а это означает, что его не нужно будет перемещать, когда мы собираем для генерации 0.
Теперь представьте, что мы выделяем новый объект кучи в генерации 0 и мутируем элемент массива для ссылки на него. Теперь рассмотрим, что происходит, когда мы мусор собираем питомник: если мы посмотрим только на ссылки в поколении 0, мы ошибочно сделаем вывод, что наш недавно выделенный объект мертв. Это, очевидно, неверно: наш новый объект находится в реальном времени через его ссылку из массива в генерации 1.
По этой причине мы должны поддерживать список изменяемых объектов для каждого поколения, которое мы будем извлекать из корней, когда мы собираем младшие поколения.
Однако, как предложила ваша интуиция, это должно быть ненужным в компактном регионе, так как мы не должны убирать содержимое региона. Более того, поскольку представление кучи MutableArray#
и Array#
идентично (кроме указателя информационной таблицы), использование unsafeCoerce#
должно быть прекрасным.
Все это, естественно, зависит от осторожного сохранения инварианта замыкания, которому подчиняются компактные области. Это требует, чтобы все вычисления в конечном итоге сводились к некоторому поиску в регионе. Строго говоря, этого недостаточно, поскольку семантика Haskell не гарантирует, что копия не будет создана, но в случае GHC Haskell это не должно быть проблемой.
Пример
Здесь небольшая игровая площадка, которую я использовал для тестирования.
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE BangPatterns #-}
import GHC.IO
import GHC.Compact
import GHC.Exts hiding (toList)
data Wombat = Wombat String deriving Show
n = 10000
wombats :: [Wombat]
wombats = take n $ cycle $ map Wombat [ "harry", "jerry", "carry", "larry", "fred" ]
main :: IO ()
main = do
c <- fromListM (length wombats) wombats >>= compact
let arr = getCompact c
flip mapM_ [1..n-1] $ \i -> do
swapMutArray i ((i+2) `mod` n) arr
mapM_ print (toList arr)
return ()
(!) :: MutArray a -> Int -> a
(!) (MutArray a) (I# n) =
case indexArray# a n of
(# x #) -> x
data MutArray a = MutArray (Array# a)
getMutArray :: MutArray a -> MutableArray# s a
getMutArray (MutArray arr) = unsafeCoerce# arr
fromListM :: Int -> [a] -> IO (MutArray a)
fromListM = \[email protected](I# n#) xs ->
IO $ \s0 -> case newArray# n# x0 s0 of
(# s1, arr #) -> unIO (go arr (n-1) xs) s1
where
x0 = error "hi"
go arr (-1) [] = IO $ \s0 -> case unsafeFreezeArray# arr s0 of
(# s1, arr' #) -> (# s1, MutArray arr' #)
go arr [email protected](I# n#) (x:xs) = do
IO $ \s0 -> case writeArray# arr n# x s0 of s1 -> (# s1, () #)
go arr (n-1) xs
go _ _ _ = error "uh oh"
toList :: Show a => MutArray a -> [a]
toList [email protected](MutArray arr#) = go 0
where
!len = I# (sizeofArray# arr#)
go n
| n == len = []
| otherwise = (arr ! n) : go (n + 1)
writeMutArray :: Int -> a -> MutArray a -> IO ()
writeMutArray (I# i) x arr =
IO $ \s0 -> case writeArray# (getMutArray arr) i x s0 of s1 -> (# s1, () #)
swapMutArray :: Int -> Int -> MutArray a -> IO ()
swapMutArray [email protected](I# m#) [email protected](I# n#) arr = do
!x <- IO $ readArray# (getMutArray arr) m#
!y <- IO $ readArray# (getMutArray arr) n#
writeMutArray n x arr
writeMutArray m y arr