提速玻璃模拟,通过限制要绘制的列表大小。

huangapple go评论58阅读模式
英文:

Speed Up Gloss Simulation by Limiting Size of Lists to Plot

问题

我正在使用Gloss模拟一个脉冲神经网络。我想在滑动图中绘制输入和生成的脉冲历史记录(固定窗口大小)。我编写了绘图函数,它们工作得很好。然而,如果我让模拟运行一分钟,因为我没有限制历史记录的大小(但我想限制在5000到10000之间),它会变得相当慢(它执行卷积以将脉冲转换回“模拟”图)。

我编写了下面的代码来限制列表长度,它可以工作,但似乎有点笨拙,而且有点慢。

limitHists :: Float -> Float -> World -> World
limitHists spike input w =
  let w' =  if wSGSpikeHistFull w  then w {wSGSpikeHist = (drop 1 (wSGSpikeHist w) ++[spike])}
                   else if length  (wSGSpikeHist w) > 5000
                           then w  {wSGSpikeHistFull = True}
                           else w {wSGSpikeHist = (wSGSpikeHist w) ++ [spike]}
      w'' = if wSGInputHistFull w' then w'{wSGInputHist =  (drop 1 (wSGInputHist w') ++ [input])}
                   else if length  (wSGInputHist w) > 5000
                           then w' {wSGInputHistFull = True}
                           else w' {wSGInputHist = (wSGInputHist w) ++ [input]}
  in w''

我正在为每种“历史”类型复制代码。似乎Haskell不允许我抽象字段名称。

尽管我限制了列表大小,但性能分析器显示大约有10%的时间用于执行这个函数... 显然有一些O(n)的操作。

所以,我有两个问题...

  1. 有没有办法在我的代码中避免“不要重复你自己”?

  2. 我应该使用其他一些更快的技术来做到这一点吗?

  • Data.Sequence?
  • Data.Array?
  • Data.Vector?
  • Data.Circular.List?
  • Okasake摊销?

问题可能是,由于模拟以60fps运行,上述数据结构必须每帧执行一个类似于“toList”的操作,以使Gloss满意。

我知道其中一些可能需要尝试和错误才能找出哪个更快,但我只是想看看是否漏掉了一些明显的东西。

任何帮助都将不胜感激,

谢谢,

汤姆

英文:

I am using Gloss to simulate a spiking neural network .. I want to plot the input and generated spike histories in sliding graphs (fixed window size). I wrote the plotting functions and they work well. However, if I let the sim run for a minute, since I'm not limiting the size of the histories (but I'd like to, somewhere between 5000 and 10000), it's quite slow (it performs a convolution to convert spikes back to an 'analog' plot).

I wrote the code below to limit the list lengths, which works, but it seems a bit kludgy .. and somewhat slow.

limitHists :: Float -> Float -> World -> World
limitHists spike input w =
  let w' =  if wSGSpikeHistFull w  then w {wSGSpikeHist = (drop 1 (wSGSpikeHist w) ++[spike])}
                   else if length  (wSGSpikeHist w) > 5000
                           then w  {wSGSpikeHistFull = True}
                           else w {wSGSpikeHist = (wSGSpikeHist w) ++ [spike]}
      w'' = if wSGInputHistFull w' then w'{wSGInputHist =  (drop 1 (wSGInputHist w') ++ [input])}
                   else if length  (wSGInputHist w) > 5000
                           then w' {wSGInputHistFull = True}
                           else w' {wSGInputHist = (wSGInputHist w) ++ [input]}
  in w''

I'm duplicating the code for each 'history' type. It seems that Haskell doesn't allow me to abstract the field names.

Even though I limit the list size the profiler says it takes about 10% of the time just to do this function.. apparently there's some O(n) going on.

So, I have two questions ..

  1. Is there a way to 'Don't Repeat Yourself' in my code?

  2. Should I be using some other technique to do this that's faster ?

  • Data.Sequence ?
  • Data.Array ?
  • Data.Vector ?
  • Data.Circular.List ?
  • Okasake amortization ?

The problem might be that since the sim is running at 60fps then the above data structures have to do a 'toList' equivalent each frame to make Gloss happy.

I know that some of this may be trial and error to find out what's faster but I just wanted to see if I was missing something obvious.

Any help appreciated,

Thanks,

Tom

答案1

得分: 2

以下是翻译好的内容:

显而易见的不重复自己的方式是定义一个 Hist 类型,并让 World 使用两个 Hist,类似于这样:

data Hist = Hist { hist :: [Float], histFull :: Bool }

data World = World { spikeHist :: Hist, inputHist :: Hist }

可能还需要一个函数来添加一个值:

addDatum :: Float -> Hist -> Hist

你可以通过自己跟踪项目的数量来避免一直重新计算长度。这个计数会使 histFull 标志变得不必要,所以如果有的话,这会使逻辑更简单。

然而,你的想法是正确的,列表不是这里的最佳数据结构。我们可以避免计算长度,但不能避免添加项目到末尾,这也是线性时间的操作。(我们可以将列表保持在逆序,这样我们可以在前面添加项目,但然后我们会在末尾删除它们,这也没有改善。)

我会使用 Data.Sequence。Data.Array 不适用,因为你无法快速修改单个项目。Data.Vector 不太适用于大小变化的向量,但你可以在其上构建自己的环形缓冲区。

英文:

The most obvious way to not repeat yourself would be to define a Hist
type and have World use two of them, something like this:

data Hist = Hist { hist :: [Float], histFull :: Bool }

data World = World { spikeHist :: Hist, inputHist :: Hist }

Probably with a function to add a value:

addDatum :: Float -> Hist -> Hist

You could avoid recalculating length all the time by keeping track of
the number of items yourself. This count would make the histFull flag
unnecessary, so it would make the logic simpler if anything.

However, you are right in thinking a list is not the best data
structure here. We can avoid calculating length but we can't avoid
adding items to the end which is also linear time. (We could keep the
list in reverse order so we could add items at the front, but then
we'd be deleting them at the end which is no better.)

Data.Sequence is what I would use. Data.Array isn't suitable
because you can't quickly modify single items. Data.Vector isn't
really for vectors that change size but you could maybe build your own
ring buffer on top of it.

答案2

得分: 1

大卫的答案很好。我想提到另一种非常简单的数据结构来实现队列,即一对列表。一个存储队列的“前端”顺序,另一个存储队列的“后端”逆序。

type Q a = ([a], [a])

push :: a -> Q a -> Q a
push a (front, back) = (front, a:back)

-- 为了简化演示,如果队列为空,它会无限循环
pop :: Q a -> (a, Q a)
pop (a:front, back) = (a, (front, back))
pop ([], back) = pop (reverse back, [])

这相当简单。但是,如果您需要定期以顺序遍历整个队列,可能不太适合。这似乎对卷积可能会有问题,但不一定如此。您可以将您的序列与队列的“前端”(零填充)卷积,然后将您的序列的逆序与队列的“后端”(零填充)卷积,然后通过将重叠的输出相加来连接两个结果中间。

英文:

David's answer is a good one. I thought it might be fun to mention another very simple data structure for implementing a queue, namely, a pair of lists. One stores the "front" of the queue in forward order, and the other stores the "back" of the queue in reverse order.

type Q a = ([a], [a])

push :: a -> Q a -> Q a
push a (front, back) = (front, a:back)

-- for simplicity of presentation, this infinitely loops if the queue is empty
pop :: Q a -> (a, Q a)
pop (a:front, back) = (a, (front, back))
pop ([], back) = pop (reverse back, [])

It's pretty simple. However, if you need to regularly traverse the entire queue in order, it may not be suitable. That seems like it might spell trouble for a convolution, but it doesn't have to. You can convolve your sequence with the (zero-padded) front of the queue, and convolve the reverse of your sequence with the (zero-padded) back of the queue, then join the two results in the middle by adding together the overlapping outputs.

huangapple
  • 本文由 发表于 2023年5月30日 09:34:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76361109.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定