Mutable Array в компактном регионе GHC

Я пытаюсь выяснить, можно ли положить MutableArray# в компактная область. Это явно не рекомендуется ghc devs и документация потому что он позволяет пользователям указывать на что-то вне Compact. В стороне, я все еще заинтересован в попытке сделать это, с пониманием что я буду нести ответственность за то, чтобы массив указывал только на вещи внутри того же Compact.

Мое мышление заключалось в том, чтобы добавить Array# в компактную область, а затем попытайтесь оттереть его, используя unsafeThawArray#:

unsafeThawArray# :: Array# a -> State# s -> (#State# s, MutableArray# s a#)

Затем я мог бы использовать writeArray#, если (а) все, что я запись в MutableArray# находится в одной и той же компактной области и (b) I перед тем, как записать его в массив, оцените все в WHNF с помощью seq. Я думаю, это было бы безопасно. У меня есть одна проблема, основанная на stg_unsafeThawArrayzh комментарий:

MUT_ARR_PTRS живет в изменяемом списке, но MUT_ARR_PTRS_FROZEN обычно не...

Я не очень хорошо понимаю внутренности GHC, но вот мое понимание комментария: Есть что-то, называемое изменчивым списком, в котором есть куча изменчивых массивов и время от времени сканируется GC. Для моих целей это проблематично, поскольку это означает что unsafeThawArray# приведет к тому, что GC начнет сканирование вещей в компактном регионе. Это нехорошо. Но, может быть, мое понимание ошибочно, и это было бы здорово.

Если unsafeThawArray# не может делать то, что мне нужно, я думал, что unsafeCoerce# будет сделайте это, но я снова хотел бы услышать от кого-то, знающего эту тему. Спасибо и дайте мне знать, если я могу что-то прояснить.

EDIT: просто комментарий для будущих читателей. Подумав об этом больше, я понял, что лучше использовать SmallArray# вместо Array#. Это должно делать записи быстрее. Сравните код запись в MutableArray # с кодом для запись в SmallMutableArray #. Карточный стол, который MutableArray# сохраняет обновленным, бесполезен, когда все находится на компактной куче, так как он никогда не будет отсканирован.

Ответы

Ответ 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