我目前正在探索使用基本容器为FRP网络提供更多结构的可能性,从而更容易创建更复杂的事件网络。
注意:我使用ordrea但reactive-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.generate
和Vector.imap
的包装。
M.clampedIndex
将索引限制在矩阵的边界内。Event
是Monoid
的一个实例,这就是为什么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
到目前为止,我已经做了以下观察:
Data.Matrix.foldMap
(即fold
)分配的内存最多(约为45%,按照-p
)
当我还在使用 reactive-banana 时,Heinrich Apfelmus建议基于树的遍历更适合行为¹。我尝试sequenceA
,fold
和traverse
但没有成功。
此时我花了大部分时间寻找这个问题的解决方案。直觉上我说采样应该快得多,并且foldMap
不应该创建如此多的垃圾内存。有什么想法吗?