Как сохранить очень большие элементы на память без изнурительного сборщика мусора?

голоса
15

В Haskell, я создал вектор из IntMaps 1000000. Затем я использовал Gloss , чтобы сделать снимок в пути , который обращается случайное intmaps из этого вектора.
То есть, я должен был держать каждый из них в памяти. Функция рендеринга сам по себе очень легкая, так что производительность должна была быть хорошими.
Тем не менее, программа работает на 4 кадра. После профилирования я заметил 95% времени было потрачено на GC. Справедливо:
ГЙ бешено сканирование моего вектора, даже если он никогда не меняется.

Есть ли способ сказать GHC «это большое значение, необходимое и не изменится - не пытаться собрать что - нибудь в этом» .

Изменить: ниже программы достаточно повторить вопрос.

import qualified Data.IntMap as Map
import qualified Data.Vector as Vec
import Graphics.Gloss
import Graphics.Gloss.Interface.IO.Animate
import System.Random

main = do
    let size  = 10000000
    let gen i = Map.fromList $ zip [mod i 10..0] [0..mod i 10]
    let vec   = Vec.fromList $ map gen [0..size]
    let draw t = do 
            rnd <- randomIO :: IO Int
            let empty = Map.null $ vec Vec.! mod rnd size
            let rad   = if empty then 10 else 50
            return $ translate (20 * cos t) (20 * sin t) (circle rad)
    animateIO (InWindow hi (256,256) (1,1)) white draw

Это получает доступ к случайным картам на огромный вектор и рисует вращающийся круг, радиус которого зависит от того , является ли карта пустой.
Несмотря на то что логика является очень простой, программа борется около 1 FPS здесь.

Задан 14/05/2015 в 17:58
источник пользователем
На других языках...                            


3 ответов

голоса
4

глянец является виновником здесь.

Во-первых, немного фона на сборщика мусора GHC в. GHC использует (по умолчанию) целое поколение, копирование сборщика мусора. Это означает, что куча состоит из нескольких областей памяти, называемых поколениями. Объекты выделяются в молодое поколение. Когда поколение переполняется, он проверяется на наличие живых объектов и живые объекты копируются в следующий старшего поколения, а затем поколение, которое сканировали помечен как пустой. Когда старшее поколение переполняется, живые объекты, а не копируются в новую версию самого старшего поколения.

Важный факт , чтобы забрать из этого является то, что GC только когда - либо исследует живые объекты. Мертвые объекты никогда не трогал вообще. Это здорово , когда сбор поколений , которые в основном мусор, как это часто бывает в молодом поколении. Это не хорошо , если долгоживущие данных подвергается многим ШС, так как он будет скопирован многократно. (Это также может быть нелогичным , которые используются для управления памятью таНос / вольная, где распределение и освобождение оба являются весьма дорогостоящими, но оставляя объекты , выделенные в течение длительного времени не имеет прямых затрат) .

Теперь, «гипотеза поколений» является то, что большинство объектов либо кратковременные или долгоживущие. В долгоживущих объектах быстро попадают в старшем поколении, так как они живы в каждой коллекции. В то же время, большинство из короткоживущих объектов, которые выделяются не переживет молодое поколение; только те, которые случаются быть живым, когда он собирается будут продвигаться к следующему поколению. Кроме того, большинство из этих короткоживущих объектов, которые получают продвигаемые не доживет до третьего поколения. В результате, старшее поколение, который держит в долгоживущие объекты должны заполнить очень медленно, и его дорогие коллекции, которые должны скопировать все долгоживущие объекты должны происходить редко.

Теперь, все это действительно так в вашей программе, за исключение одной проблемы:

    let displayFun backendRef = do
            -- extract the current time from the state
            timeS           <- animateSR `getsIORef` AN.stateAnimateTime

            -- call the user action to get the animation frame
            picture         <- frameOp (double2Float timeS)

            renderS         <- readIORef renderSR
            portS           <- viewStateViewPort <$> readIORef viewSR

            windowSize      <- getWindowDimensions backendRef

            -- render the frame
            displayPicture
                    windowSize
                    backColor
                    renderS
                    (viewPortScale portS)
                    (applyViewPortToPicture portS picture)

            -- perform GC every frame to try and avoid long pauses
            performGC

глянец говорит GC, чтобы собрать старшее поколение каждый кадр!

Это может быть хорошей идеей , если эти коллекции , как ожидается, займет меньше времени , чем задержка между кадрами в любом случае, но это явно не является хорошей идеей для вашей программы. Если вы удалите этот performGCвызов из глянца, то ваша программа работает довольно быстро. Предположительно , если вы позволите ему работать достаточно долго, то старшее поколение будет в конечном итоге заполнить и вы можете получить задержку на несколько десятых долей секунды , как GC копии всех ваших долгоживущие данных, но это намного лучше , чем платить , что стоимость каждый кадр.

Все , что сказал, есть билет # 9052 о добавлении стабильного поколения, которые также удовлетворить ваши потребности хорошо. Смотрите здесь для более подробной информации.

Ответил 15/05/2015 в 20:33
источник пользователем

голоса
2

Я хотел бы попробовать скомпилировать , -with-rtsoptsа затем играть с кучей ( -H) и / или распределителем ( -Aопции). Те , существенно влияют как работает GC.

Более подробная информация здесь: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/runtime-control.html

Ответил 14/05/2015 в 23:47
источник пользователем

голоса
1

Для того, чтобы добавить ответ Рейда, я нашел performMinorGC(добавлено в https://ghc.haskell.org/trac/ghc/ticket/8257 ) , чтобы быть лучшим из обоих миров здесь.

Без какого - либо явного планирования GC, я все еще получаю частый кадр коллекции , связанные с каплями , как питомник истощается. Но на performGCсамом деле становится производительность убийства , как только какие - либо существенные долгоживущие использования памяти.

performMinorGC делает то, что мы хотим, не обращая внимания долговременную память и очистку от мусора каждого кадра предсказуемо - особенно, если вы настроитесь -H и -Aобеспечить, чтобы мусор в кадре помещается в детскую.

Ответил 14/03/2016 в 03:10
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more