Performance batch processing Messages

I have the following edge case:

  • I am generating large lists of data to create a world map for a game
  • The generation process is batch processing a least 1000-1500 Messages but it can be way more depending on the world map size
  • Since it would block the main thread if I would do it synchronously, I subscribed to Time.every 0 TakeMessageAndProccessIt to process each message (not animation frame , this would be too slow - 60fps only). It takes about 6 seconds to process about 1400 Messages, and I noticed that about 4 Seconds of it, it is idle ! I understand why there is idle time, but why is it so much ? Should I process multiple Messages in each step (Problem is also that some Messages in the Batch use Cmd with Random.generate ) ?

Here a small footprint of my Performance Profile:

Can you see the idle gaps ?
So my question is: Is there a way to make this better ? I also thought of a Web Worker to process this long calculations …

These are my messages for each field in the map: (Except DroppedGenerationDataForPerformance , SetCurrentEcoSystemType , SetBiomeList , EndStep )

type GenerationStepMsg
    = DroppedGenerationDataForPerformance
    | SetCurrentEcoSystemType EcoSystemType
    | SetBiomeList (List Biome)
    | RollRandomCoordinate (List WorldSpace -> Random.Generator Int) (List WorldSpace)
    | RollRandomBiome (List Biome -> Random.Generator Int)
    | RollChunkTreesSubCoordinates (List.Nonempty.Nonempty Tree -> Random.Generator (List ScreenSpace))
    | RollChunkTreeTypes (List.Nonempty.Nonempty Tree -> Random.Generator (List TreeType))
    | AddChunkToGlobalList
    | DropPickedBiomeFromBiomeList
    | CreateChunk (EcoSystemType -> Biome -> WorldSpace -> Chunk)
    | CalculatePossibleCoordinates (List Chunk -> List WorldSpace)
    | EndStep

So it seems likely here that messages don’t contain enough work, and the elm runtime has some maximum refresh rate.

How much of the messages are use Random? if that’s a lot, a possible partial solution is to keep track of the random seed manually, and use Random.step to synchronously get some randomness and continue processing without going through update.

1 Like

Wow, yeah that made a great speedup. Thanks a lot ! I refactored to Random.step and am now processing a batch of 50 messages per update: The time went from 6000ms to 150ms !

1 Like

It may be worth mentioning that timeouts in the browser have a granularity of a few milliseconds. For some browsers it may be 4 or 1 but it is definitely not immediate.

window.requestIdleCallback and ports may be a better option for this.

Rather than susbscribing to Time.every 0 , just process some messages in your update then create a new Cmd to process some more. Use this code to create the messages:

Task.perform identity (Task.succeed TakeMessageAndProccessIt)

That will go faster (but still not 100% cpu), and allow the application to remain responsive. You can trade off responsiveness against larger batches of work - its not ideal and you will notice responsiveness suffer.

Something along these lines:

update msg model = 
    case msg of
        TakeMessageAndProcessIt ->
            case model.queue of
                [] -> (model, Cmd.none)
                _ -> (processNextBatch model
                     ,Task.perform identity (Task.succeed TakeMessageAndProccessIt))
        ...
1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.