为什么折叠事件和行为会占用如此多的内存?

时间:2014-11-20 12:55:53

标签: haskell fold frp

我目前正在探索使用基本容器为FRP网络提供更多结构的可能性,从而更容易创建更复杂的事件网络。

注意:我使用ordreareactive-banana也遇到了同样的问题,所以我猜这个问题不是特定于所选的frp实现。


在这种特殊情况下,我使用简单的Matrix来存储Events

newtype Matrix (w :: Nat) (h :: Nat) v a where
   Matrix :: Vector a -> Matrix w h v a

-- deriving instances: Functor, Foldable, Traversable, Applicative

Matrix基本上只是Data.Vector的一个薄包装,我使用的大多数函数与相应的Vector函数基本相同。值得注意的例外是索引,但这应该是自我解释的。


有了这个,我可以定义像Matrix 10 10 (Event Double)这样的事件矩阵,并能够定义基本的卷积算法:

applyStencil :: (KnownNat w, KnownNat h, KnownNat w', KnownNat h')
             => M.Matrix w' h' (a -> c)
             -> M.Matrix w h (Event a)
             -> M.Matrix w h (Event c)
applyStencil s m = M.generate stencil
  where stencil x y = fold $ M.imap (sub x y) s
        sub x0 y0 x y g = g <$> M.clampedIndex m (x0 - halfW + x) (y0 - halfH + y)
        halfW = M.width s `div` 2
        halfH = M.height s `div` 2

备注:

  • M.generate :: (Int -> Int -> a) -> M.Matrix w h a

    M.imap :: (Int -> Int -> a -> b) -> M.Matrix w h a -> M.Matrix w h b

    分别是Vector.generateVector.imap的包装。

  • M.clampedIndex将索引限制在矩阵的边界内。
  • EventMonoid的一个实例,这就是为什么fold只能Matrix w' h' (Event c)返回M.imap (sub x y) s的原因。

我的设置大致如下:

let network = do
  -- inputs triggered from external events 
  let inputs :: M.Matrix 128 128 (Event Double)

  -- stencil used:
  let stencil :: M.Matrix 3 3 (Double -> Double)
      stencil = fmap ((*) . (/16)) $ M.fromList [1,2,1,2,4,2,1,2,1]

  -- convolute matrix by applying stencil
  let convoluted = applyStencil stencil inputs

  -- collect events in order to display them later
  -- type: M.Matrix 128 128 (Behavior [Double])
  let behaviors = fmap eventToBehavior convoluted

  -- now there is a neat trick you can play because Matrix
  -- is Traversable and Behaviors are Applicative:
  -- type: Behavior (Matrix 128 128 [Double])
  return $ Data.Traversable.sequenceA behaviors

使用这样的东西我触发~15kEvents / s没有任何问题,并且在这方面有很多空间。

问题是,一旦我对网络进行采样,我每秒只能得到大约两个样本:

main :: IO ()
main = do

  -- initialize the network
  sample <- start network

  forever $ do

    -- not all of the 128*128 inputs are triggered each "frame"
    triggerInputs

    -- sample the network
    mat <- sample

    -- display the matrix somehow (actually with gloss)
    displayMatrix mat

到目前为止,我已经做了以下观察:

  • 分析告诉我生产率非常低(4%-8%)
  • 大部分时间是第1代垃圾收集器(~95%)
  • Data.Matrix.foldMap(即fold)分配的内存最多(约为45%,按照-p

  • 当我还在使用 reactive-banana 时,Heinrich Apfelmus建议基于树的遍历更适合行为¹。我尝试sequenceAfoldtraverse但没有成功。

  • 我怀疑newtype包装器阻止向量融合规则触发²。这很可能不是罪魁祸首。

此时我花了大部分时间寻找这个问题的解决方案。直觉上我说采样应该快得多,并且foldMap不应该创建如此多的垃圾内存。有什么想法吗?

0 个答案:

没有答案