How to manage long running computations in Elm?

489 views
Skip to first unread message

Rupert Smith

unread,
Jun 6, 2017, 10:20:42 AM6/6/17
to Elm Discuss
The problem with long running computations in a single threaded UI is that they may cause the UI to block and become unresponsive for a period. Definitely not an acceptable user experience.

I have created a library for helping with AI-type searches. This provides a 'next' function that steps the search along by examining a single 'node' in the search space. The search space forms a graph, and as each node is de-queued from the search buffer, it is checked to see if it is a goal state, and all its successor states are created from a user supplied 'step' function, and en-queued onto the search buffer.


next : SearchResult state -> SearchResult state

type SearchResult state
    = Complete
    | Goal state (() -> SearchResult state)
    | Ongoing state (() -> SearchResult state)

So I can run just 1 or perhaps a few hundred iterations of the search in each loop around the Elm 'update' function. I can return a Cmd that will use the cause the () -> continuation to run some more iterations next time, and that will also give control back to the Elm kernel to process other Cmds and keep the UI responsive.

Its explicit time-slicing of the CPU in application code, which is not so nice.

Ideally, I would have a background thread running the search. On every iteration it would check a volatile (shared memory) flag in case the user got bored waiting for the results and clicked cancel. 

I know there have been a few experiments in hooking up Elm with webworker threads. Has anyone tried this out? In particular was there a way to do inter-process communication, specifically the cancel flag I described above?

There is some IPC stuff mentioned under 'Future Plans' (spawn/kill/send) in the Process module:


Could this be implemented on top of webworker threads?

Rupert Smith

unread,
Jun 7, 2017, 4:08:37 AM6/7/17
to Elm Discuss
On Tuesday, June 6, 2017 at 3:20:42 PM UTC+1, Rupert Smith wrote:
The problem with long running computations in a single threaded UI is that they may cause the UI to block and become unresponsive for a period. Definitely not an acceptable user experience.

I have created a library for helping with AI-type searches. This provides a 'next' function that steps the search along by examining a single 'node' in the search space. The search space forms a graph, and as each node is de-queued from the search buffer, it is checked to see if it is a goal state, and all its successor states are created from a user supplied 'step' function, and en-queued onto the search buffer.

I should add, it seems likely any long-running process will be structured to have a loop at the top-level. This loop can be turned into a continuation '() -> More a', to break the task down into steps. So this does not just apply to my AI search library but any longer computation we might want to do that puts too much delay into the UI update cycle.

Matthieu Pizenberg

unread,
Jun 12, 2017, 11:10:15 PM6/12/17
to Elm Discuss
On Wednesday, June 7, 2017 at 4:08:37 PM UTC+8, Rupert Smith wrote:
On Tuesday, June 6, 2017 at 3:20:42 PM UTC+1, Rupert Smith wrote:
The problem with long running computations in a single threaded UI is that they may cause the UI to block and become unresponsive for a period. Definitely not an acceptable user experience.

Yep, totally agree with you. I also would love to be able to run some scientific computation, but the UI blocking is a stopper.

Vlad GURDIGA

unread,
Jun 13, 2017, 11:26:33 AM6/13/17
to Elm Discuss
One thing that I think I would have tried in this case, it to delegate the long running computation to a webworker, and then talk with it through a port.

Rupert Smith

unread,
Jun 13, 2017, 5:08:05 PM6/13/17
to Elm Discuss
On Tuesday, June 13, 2017 at 4:26:33 PM UTC+1, Vlad GURDIGA wrote:
One thing that I think I would have tried in this case, it to delegate the long running computation to a webworker, and then talk with it through a port.

Yes, and I see Richard Feldman has had an experiment with Elm and webworkers:


Its for Elm 0.17 so I guess just an experiment, but worth a look.

Rupert Smith

unread,
Jun 13, 2017, 5:10:30 PM6/13/17
to Elm Discuss
But you can split the computation at some suitable point, returning a continuation to 'calculate the rest later', then return to your 'update' function and have it return a Cmd to come back in later and continue the work. Did you try this technique? Its not ideal, but at least it makes it no longer a stopper.

Matthieu Pizenberg

unread,
Jun 13, 2017, 9:39:57 PM6/13/17
to Elm Discuss
One thing that I think I would have tried in this case, it to delegate the long running computation to a webworker, and then talk with it through a port.

I'm not from the web community. I had bases in html+css+js, but pretty much had to relearn my JS when started to try elm, so I've never used web workers before. Not sure I'd like to take some time to learn how it works.


On Wednesday, June 14, 2017 at 5:10:30 AM UTC+8, Rupert Smith wrote:
Did you try this technique? Its not ideal, but at least it makes it no longer a stopper.

Nope, but thanks, I keep the idea in mind. Actually, It wasn't the only issue I had since this was mainly image manipulation. The lack of binary data type support was also very restricting. Then I didn't want to invest too much energy in this. I prefer to wait and see for now, busy with other matters. I'm still confident in the fact that elm is a great language and evolves slowly but surely. I'm just here a bit too early for some of my needs. For the rest, I'm very happy with elm.

Jakub Hampl

unread,
Jun 30, 2017, 4:43:51 AM6/30/17
to Elm Discuss
One way of approaching this is to show the user progress in an interesting way. In this example, the computation that calculates the final layout of a network graph (which is pretty expensive) is animated so the user can watch the algorithm converge.

Rupert Smith

unread,
Jul 2, 2017, 1:59:46 PM7/2/17
to Elm Discuss
On Friday, June 30, 2017 at 9:43:51 AM UTC+1, Jakub Hampl wrote:
One way of approaching this is to show the user progress in an interesting way. In this example, the computation that calculates the final layout of a network graph (which is pretty expensive) is animated so the user can watch the algorithm converge.

Nice example, I like how you can interrupt it by clicking on one of the other examples, so the UI is not getting frozen.

One thing about this example, is that is driven off of a timer tick. So if each iteration of the node layout takes say one microsecond of CPU time, and the timer ticks every millisecond - it will only use 1/1000th of the CPU. Its fine for animation.

In my case I don't want to use a timer tick to drive the computation, because I want to use 100% of the CPU (or close to it) to complete a computation as quickly as possible, but still not block user interaction.

We had a good first Elm Scotland Meetup btw, hope you can make the next one.

Jakub Hampl

unread,
Jul 3, 2017, 4:42:09 AM7/3/17
to elm-d...@googlegroups.com
In JS, you could measure the time of each iteration and then only do so many that it would fit into a “frame budget” (so < 16ms, or if you are updating the DOM you would probably only spend say 10ms). However, I’m not sure how easy that would be in Elm.
--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/M5teKjboylI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Rupert Smith

unread,
Jul 3, 2017, 9:30:31 AM7/3/17
to Elm Discuss
On Monday, July 3, 2017 at 9:42:09 AM UTC+1, Jakub Hampl wrote:
In JS, you could measure the time of each iteration and then only do so many that it would fit into a “frame budget” (so < 16ms, or if you are updating the DOM you would probably only spend say 10ms). However, I’m not sure how easy that would be in Elm.

Yes, I was considering that. I have a function that takes an argument 'n' which tells it how many loops of the search to run (or terminate sooner if it finds a goal state). I was thinking of running say 10 iterations and timing how long that takes, then using the throughput from that (= num iterations / time taken) to estimate how many iterations would fit in what you call a "frame budget". Then on subsequent iterations try and do a frame budgets worth each time.

Could work. Or web workers. 
Reply all
Reply to author
Forward
0 new messages