Timely optimization, part 3

Avoiding expensive list operations

Peter Ward
Nerd For Tech
4 min readNov 30, 2021

--

GPXmagic allows the user to change the way that a GPS track is displayed.
Rendering options in GPXmagic

Previously, I wrote about the effect of switching from simple list search to using a two-dimensional index (a type of quadtree), and the impact of moving code from the application at large into the data structure, reducing the overheads of creating results sets only for them to be filtered or reduced in some way but essentially discarded.

The widespread use of lists is part and parcel of GPXmagic however. It’s an editor and visualiser for lists of GPS coordinates; that’s what a GPX file is Being many-times bitten by premature optimisation, I am generally fairly careful to avoid any. for example, as you can see in the screenshot above, there’s a few options for how to visualise a track.

Up until now, I have not tried to optimise this. I do most of my functional testing on small to moderate tracks, with perhaps a few thousand GPS points. Here’s a snippet of code that I use to render.

It makes a pass across the list for each of the five possible elements, each produces a list of WebGL visual elements, which are concatenated at the end. It’s fairly clear. It runs reasonably quickly. Is there a problem?

Imagine this running over one of my larger examples. It has 23,000 points. At worst, we’re traversing this five times. Each traversal constructs an even longer list of visual elements. We may then have five lists of (say) 50,000 elements each. These are then concatenated into a final result of 250,000 elements. The partial lists are then discarded for the garbage collector to recycle. Our peak memory usage is far higher than it need be.

It’s actually worse, since mapOverPairs casually adds to the overhead:

To get to the point, I’m trying to reduce memory usage in general, and it’s obvious from the browser developer console that adding graphic elements consumes tens of megabytes, so this seems like something worth trying.

All we need do is restructure the control flow so that we make one pass over the track and apply all the active options for each point, and eliminate the mapOverPairs while we're at it. Yet, I don't want to just move the if ... structure inside my traversal; that seems silly given that they can't possibly change during the operation.

I ended up with this:

Three blocks to explain.

Firstly, scenePainterFunctions is the original if ... statement modified to return either Just <function> or Nothing according to the options set, which is then filtered to leave a list of the functions that will paint the selected features.

Secondly, paintScenePart is the function we use to make a single traversal of the points. For each point, it apply each of the selected rendering functions, each of which returns a small list of visual elements. These are combined into a single list for each point and pushed onto the start of our accumulating list.

Thirdly, scene merely applies the paintScenePart operation to each point.

The helper functions combineLists and reversingCons simply put lists together efficiently, when the order of the elements is not important.

Big question: does it make any difference? To be honest here, I’ struggling to figure this out. The browser developer tools not unreasonably assume you’re a JavaScript developer. I’m not. I’m an Elm developer, and Elm compiles to dense, obscure JavaScript. Hence, the rather smart tools for looking at memory allocation are hard to interpret. The outcome is “I think so”, though there’s an element there of “I so want it to be so”. Nonetheless, I like the shape of the code and I shall sleep more soundly.

--

--

Peter Ward
Nerd For Tech

I ride with the “mid-week professionals”, write stories and technical stuff, and enjoy the low-stress life of semi-retirement.