In a hot loop more than 50% of my time is spent in...
# announcements
d
In a hot loop more than 50% of my time is spent in a call to
launch
. How can I launch coroutines from a non-suspend function in a fire-and-forget way faster?
m
You could instead put work into a
Channel
and have another coroutine go through all work in the channel and start a coroutine for each of them. How many are you starting? 😮
d
Doesn't you need to be in suspend to send to a channel?
b
You do. I'd write a non suspend helper that'd send an action to the channel via GlobalScope.launch
r
why not put this loop inside a suspending function?
m
No, you can send from non-suspend to a buffered channel using
.offer()
d
My original problem is that calling GlobalScope.launch is slow
Oh, thank you!
Also, is their an easy way to deduplicate insertions to a buffered channel?
m
No, you’d have to deduplicate on the receiving end
deduplication ain’t cheap 🙂
d
Yeah...
The hot loop is the android onDraw callback
😬 1
I simplified onDraw by making it request remote data every loop, and then deduplicating the requests
m
Sounds more like an architectural problem if you need coroutines or channels in
onDraw
😅 Is it for some kind of asynchronous drawing?
âž• 2
d
I'm making a map app, so onDraw draws a bunch of tiles corresponding to the current position. It checks a cache for each tile, and if it is cached it displays it. Otherwise it displays a placeholder and calls a method in a Repo to asynchronously fetch the tile and add it to the cache
m
I see. I guess the channel approach makes most sense here if
launch
is really the curlpit. Also remember that
launch
causes a memory allocation for the coroutine function. Using a channel you could avoid that.
d
First I'm going to try batching the messages so I only send once per onDraw
Can you think of any architectural changes that would avoid this problem? I'm not attached to mine general architecture at all, it's just the least-bad one i've tried so far
r
wouldn't it be easier to have a simple fifo queue with missing tiles id's, processed by a background coroutine?
d
Where do I do the deduplication then? Does the View keep track of what it's added to the queue recently?
m
I’d probably try to find a way where I don’t need to do anything in
onDraw
related to preloading. Can’t you find out in advance what tiles are in the rendering area e.g. in layouting or when scrolling? Then queue tile loading there and once a new tile became available ask the view to re-draw the relevant part of the view.
âž• 1
d
That makes sense
I think I would still need to deduplicate somewhere, as in pans most of the tiles are the same as the previous state
I switched from redrawing in a callback to redrawing every animation frame because I got the same energy/cpu/memory usage and it made things simpler
m
That shouldn’t be too much work. How much tiles will you display at once? Probably a handful. Jus check what tile indexes you’ve already queued and skip those. Where is the hot loop coming from anyway? How many tiles are there?
d
Depending on the zoom level, 20-50
The hot loop comes from animation pan/zoom
I redraw all the tiles every step of a pan or zoom
m
You mean when zooming far out?
Ah I misread that
50 isn’t that much
d
Yeah
m
Cant you optimize that by just pre-computing what frame is currently covered by the tiles and as soon as the new draw/layout extends beyond that frame trigger an update? That update should happen much rarer than on every single pan/zoom event.
d
Do you mean draw to a canvas bigger than the screen and pan/zoom that?
The frame is pretty small, because each tile is around 250px by 250px
m
hmm on iOS that would be easy. I don’t know on Android. Drawing outside the view seems to be problematic there.
d
I also don't think the draws themselves cost very much
30 draws or so is a small fraction of 30 `launch`s, so I'll fix that first
m
Why do you launch a coroutine for each anyway? That’s unnecessary overhead. Just have one coroutine that processes the queue (channel) sequentially. Just fill that queue with new tiles as needed.
d
That's what I'm working on now
I think I want a pool of coroutines processing the queue. I can't find any good resources for how to do that with channels. Any suggestions?
m
I’ve written something similar a few days ago: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
You don’t need much info for each drawing, right? minTileX .. maxTileX minTileY .. maxTileY if min/max unchanged redraw what’s cached (b/c position changed) and don’t draw what’s not cached (already queued) else cancel queued ops for old tiles queue missing tiles draw cached tiles save new min/max
Not sure if that’ll work 😄
d
Each draw requires a bit more, because I need to figure out the set of tiles closest to the current zoom level. But not too much
m
but the if branch is hot and the else is probably cold - the one that queues
yeah that’s the min/max calculation, isn’t it?
x * y tiles depending on zoom level and pan
d
Yeah, except I have a few different maps essentially and I pick one that fits the zoom level (like it shows trails on extreme zoom in, and only highways/cities at extreme zoom out)
m
ok, so one more property to keep track and diff with previous draw 🙂
d
Yeah