haskell - How to keep very big elements on memory without exhausting the garbage collector? -
in haskell, created vector of 1000000 intmaps. used gloss render picture in way accesses random intmaps vector.
is, had keep every single 1 of them in memory. rendering function lightweight, performance supposed good.
yet, program running @ 4fps. upon profiling, noticed 95% of time spent on gc. fair enough:
gc crazily scanning vector, though never changes.
is there way tell ghc "this big value needed , not change - not try collect inside it".
edit: program below sufficient replicate issue.
import qualified data.intmap map import qualified data.vector vec import graphics.gloss import graphics.gloss.interface.io.animate import system.random main = let size = 10000000 let gen = map.fromlist $ zip [mod 10..0] [0..mod 10] let vec = vec.fromlist $ map gen [0..size] let draw t = rnd <- randomio :: io int let empty = map.null $ vec vec.! mod rnd size let rad = if empty 10 else 50 return $ translate (20 * cos t) (20 * sin t) (circle rad) animateio (inwindow "hi" (256,256) (1,1)) white draw
this accesses random map on huge vector , draws rotating circle radius depend on whether map empty.
despite logic being simple, program struggles @ around 1 fps here.
gloss culprit here.
first, little background on ghc's garbage collector. ghc uses (by default) generational, copying garbage collector. means heap consists of several memory areas called generations. objects allocated youngest generation. when generation becomes full, scanned live objects , live objects copied next older generation, , generation scanned marked empty. when oldest generation becomes full, live objects instead copied new version of oldest generation.
an important fact take away gc ever examines live objects. dead objects never touched @ all. great when collecting generations garbage, happens in youngest generation. it's not if long-lived data undergoes many gcs, copied repeatedly. (it can counterintuitive used malloc/free-style memory management, allocation , deallocation both quite expensive, leaving objects allocated long time has no direct cost.)
now, "generational hypothesis" objects either short-lived or long-lived. long-lived objects end in oldest generation since alive @ every collection. meanwhile, of short-lived objects allocated never survive youngest generation; happen alive when collected promoted next generation. similarly, of short-lived objects promoted not survive third generation. result, oldest generation holds long-lived objects should fill slowly, , expensive collections have copy long-lived objects should occur rarely.
now, of true in program, except 1 problem:
let displayfun backendref = -- extract current time state times <- animatesr `getsioref` an.stateanimatetime -- call user action animation frame picture <- frameop (double2float times) renders <- readioref rendersr ports <- viewstateviewport <$> readioref viewsr windowsize <- getwindowdimensions backendref -- render frame displaypicture windowsize backcolor renders (viewportscale ports) (applyviewporttopicture ports picture) -- perform gc every frame try , avoid long pauses performgc
gloss tells gc collect oldest generation every frame!
this might idea if collections expected take less time delay between frames anyways, it's not idea program. if remove performgc
call gloss, program runs quite quickly. presumably if let run long enough, oldest generation fill , might delay of few tenths of second gc copies long-lived data, that's better paying cost every frame.
all said, there ticket #9052 adding stable generation, suit needs nicely. see there more details.
Comments
Post a Comment