Reducing CPU load when using Helm

158 views
Skip to first unread message

Kaspar Emanuel

unread,
Aug 18, 2014, 3:25:10 PM8/18/14
to helm...@googlegroups.com, haskel...@haskell.org, patai....@gmail.com
Hi,

are there some simple tricks to reduce the CPU load? This simple
square rendering sits at 20% CPU even when the window size remains
constant.

import FRP.Helm
import qualified FRP.Helm.Window as Window

render :: (Int, Int) -> Element
render (w, h) = collage w h [move (100, 100) $ filled red $ square 64]

main :: IO ()
main = do
engine <- startup defaultConfig
run engine $ render <~ Window.dimensions engine

The binary fractal tree I submitted in another thread on helm-dev
sits at 100% CPU when the mouse isn't moving. It seems to me that
Helm keeps re-drawing and re-computing when it doesn't need to.

I started digging through the source a bit but it will take me a while
to understand Elerea and how it samples the inputs and causes things
to update.

Are there any quick fixes?

Ciao,

Kaspar

Kaspar Emanuel

unread,
Aug 19, 2014, 2:08:52 PM8/19/14
to haskell-cafe, helm...@googlegroups.com
Forwarding this to haskell-cafe as it failed before.

Kaspar Emanuel

unread,
Aug 19, 2014, 4:52:33 PM8/19/14
to helm...@googlegroups.com, haskell-cafe, Gergely Patai
On 18 August 2014 20:24, Kaspar Emanuel <kaspar...@gmail.com> wrote:
So I tried stepping the render function using a time-constant. But
that just causes the resulting programs to freeze on the first frame.

https://github.com/kasbah/helm/commit/9ef9607d8057100b5b7930fa2b5e85c6040d1a44

I modified the driveNetwork function from here:
www.formicite.com/dopage.php?frp/frp.html

Bit out of my element with this stuff. I'd just like to _not_ max out
the CPU with simple graphical programs.

Kaspar Emanuel

unread,
Aug 20, 2014, 5:47:11 PM8/20/14
to Gergely Patai, helm...@googlegroups.com
Hi Gergely,

thanks for the tip. Helm sets SDL.rendererFlagPresentVSync so I guess
that takes care of that?

I followed my line of investigation a bit more and found that I can
introduce a `threadDelay 50000` just after the render call in Helm.hs
[1] which doesn't impact my application visibly but reduces the CPU
load significantly.

If you look at these definitions, this is where Helm uses Elerea.

run :: Engine -> SignalGen (Signal Element) -> IO ()
run engine gen = finally (start gen >>= run' engine) SDL.quit

run' :: Engine -> IO Element -> IO ()
run' engine smp = do
continue <- run''

when continue $ do
smp >>= render engine
threadDelay 50000 --added by me
run' engine smp

`run''` just returns false on a window close event. So really I feel
like the `start gen` call should be looking at the input signals and
seeing that nothing needs updating and therefore not calling run' as
frequently as it does.

Does that make sense? Is it achievable?

Cheers,

Kaspar

[1]: https://github.com/switchface/helm/blob/0d96499de9fec0d95fdcf62db1b650c8a8fa9746/src/FRP/Helm.hs#L107


On 20 August 2014 06:08, Gergely Patai <patai....@gmail.com> wrote:
> Hi Kaspar,
>
> What you probably want is to enable vsync so your framerate doesn’t
> exceed the necessary level. I don’t know if Helm makes this easy for
> you, and unfortunately I don’t know how to do this with SDL, which is
> what Helm uses under the hood as far as I know. Just search for SDL
> vsync and see if anything useful comes up.
>
> Gergely

Kaspar Emanuel

unread,
Aug 20, 2014, 9:10:04 PM8/20/14
to Gergely Patai, helm...@googlegroups.com
On 20 August 2014 22:46, Kaspar Emanuel <kaspar...@gmail.com> wrote:
> `run''` just returns false on a window close event. So really I feel
> like the `start gen` call should be looking at the input signals and
> seeing that nothing needs updating and therefore not calling run' as
> frequently as it does

I meant, that the `smp` call should be looking at the input signals
and deciding if things need updating

I thought maybe I could do a proof of concept with something like

run :: Engine -> SignalGen (Signal (Maybe Element)) -> IO ()
run engine gen = finally (start gen >>= run' engine) SDL.quit

run' :: Engine -> IO (Maybe Element) -> IO ()
run' engine smp = do
continue <- run''

when continue $ do
s <- smp
case (s) of
Just element -> render engine element
Nothing -> do return ()
run' engine smp

But I couldn't wrap my head around StateT yet which I would need to
get the `Maybe Element` in my application dependent on previously
sampled values.

Like I said, a bit out of my depth with this stuff. Tell me if I am
just spouting nonsense.

za...@z0w0.me

unread,
Aug 20, 2014, 11:40:38 PM8/20/14
to helm...@googlegroups.com, patai....@gmail.com
I'm not a fan of changing the render signal to wrap the Maybe monad.
I think a better optimization would be to hash the Element/Shape/etc AST and then only render it
if the AST has changed. I'm sure a library would exist to do this considering how AST-heavy
most Haskell libraries are.

Don't forget that Helm started out as a hobby project of mine to learn FRP, so it's very far away
from ever being optimized to its fullest. That's why I'm trying to encourage community contributions
with bounties because I have limited time due to studies and work.

Kaspar Emanuel

unread,
Aug 22, 2014, 11:55:13 AM8/22/14
to helm...@googlegroups.com, patai....@gmail.com


On Thursday, August 21, 2014 4:40:38 AM UTC+1, za...@z0w0.me wrote:
I'm not a fan of changing the render signal to wrap the Maybe monad.

No, I was just trying to do that to get the the decision into my application context. It would have been just to see what effect it had: a proof of concept. But I got stuck at the State Monad anyway so it will require a lot more learning on my part.

I think a better optimization would be to hash the Element/Shape/etc AST and then only render it
if the AST has changed. I'm sure a library would exist to do this considering how AST-heavy
most Haskell libraries are.
 
Sounds like a plan. I guess movement and resize of the window would have to be counted as a permanent input to the AST and the rest can vary per application.

Don't forget that Helm started out as a hobby project of mine to learn FRP, so it's very far away
from ever being optimized to its fullest. That's why I'm trying to encourage community contributions
with bounties because I have limited time due to studies and work.


Sure, I am not too concerned as long as my applications don't user 100% of one core all the time like they do currently. I think this along with the caching you fixed in 0.6.1 should have the biggest impact on performance. I have opened up an issue for this and put $50 towards it.

https://github.com/switchface/helm/issues/64

Reply all
Reply to author
Forward
0 new messages