Challenging Assumptions for Better Software

Software development is a lonely quiet art where souls can easily be crushed into despair. Other hand, it’s also a place where an individual, or team, can rise to levels of triumph and creation that can feel earth changing at times.

As a largely solo developer, I find myself zeroing in on challenges that really don’t warrant the attention that I give them, but I just can’t let them go. Case in point, I’ve been playing with SVG parsing for the past couple of years because I’ve chosen this format as the best way to represent vector graphics in the apps that I develop.

Forgetting the reasons why I chose to roll my own SVG parser rather than using myriad others that already exist, I’ve set a challenge for myself to make the most performant, and most capable parser and renderer out there. I go light on the capable part, because you can spend a good decade chasing down ill specified features that are rarely used. Creating a parser that renders 90% of the stuff that’s in the wild, or that I’m likely to use, is good enough.

Whereas, I’m willing to allow a few features slide here and there, I’m laser focused on making the most performant scanner/parser on the planet. My current work is in the svgandme repository on GitHub.

As it is, the parser/scanner is pretty fast. There was this one file that was really giving me fits though. It’s one of the CIA WorldFactBook images found on Wikimedia. It’s a largish file, but not the largest one in my test suite. This file was taking upwards of 50 seconds to first render. Subsequent renders were fast, so this was obviously a problem with parsing. I isolated the parsing, and it was taking 20 seconds. It doesn’t matter what the machine configuration was, what’s important is the magnitude of the time taken. I mean, most other files would take a small fraction of a second, including files that were on the order of 40Mb. But this one…

As it turns out, there was a “one line change” that dropped this file from 20 seconds of parsing to a mere 0.5 seconds. And here is the change…

1

2

if (!st.fSource.isEqual(st.fMark))

{

What this used to be was…

1

2

if (st.fSource == st.fMark)

{

I mean, on first glance, aren’t these just the same things? Isn’t an ‘==’ operator typically implemented as a call to ‘isEqual()’ by convention? Well, yah, sure, but this is where things get interesting don’t they.

In most cases, ‘operator==’ should compare the contents of two values for equivalence. In the case of ByteSpans, this would essentially be a memcmp() of two values. In the case of the scanner though, it’s literally the pointers we want to compare, and not the contents. Comparing contents, while returning correct values, is a complete waste of time. Given how often this comparison occurs (every single time through a core loop), this was exponentially bad for performance. It’s a wonder and a testament, that this obviously poor design choice has been masked thus far. It is only this file, and maybe another largish one, that were pathological enough to expose this performance flaw.

Given how fast parsing every single other of the few thousand .svg files is, I could have just continued to ignore this particular case, and moved on with life, adding more features, patting myself on the back for completeness. But, I really do want the fastest parser on the planet. Having super fast .svg parsing opens up a world of possibilities in terms of how to create, and manage my visual assets. It’s so fast, I don’t even need to concern myself with pre-parsing, and storing stuff in a binary format for later loading. Just keep things in their raw .svg form, and move on with life.

You would think this would be a relatively easy task to track down where the performance was failing. Problem is, all the profiling tools for Visual Studio, do NOT work when you’re running ARM64 version of Windows on the M4 apple silicon. If I were on a real x86 PC, I could have run the profiling tools, seen the hot spot, and come to the conclusion a lot quicker, but I wasn’t. ChatGPT was of no help here either. It praises me for the quality of my code, and sees nothing wrong with it. It was head scratching, intuition, and ultimately a line by line code review, with an eye towards “what assumptions am I making that could be hurting performance?”

In the end, it was an “aha!!” smack the forehead realization. I suspected, and knew there was this discrepancy between operator== and isEqual, but it’s like one of those elusive dreams where you’ve saved the planet, but can’t quite remember how.

In short, if you’re writing code, it’s useful to challenge your assumptions every once in a while. The XML parser is the core component of my SVG parser. I keep going back to it, trying to make it simpler, faster, smaller, easier to maintain. In this current round, I did find the big negative performance elephant in the room, and cleaned up a few other cases as well. At some point, I’m going to have to actually build a test suite that proves the “fastest on the planet” claim. But for now, I’m just going to keep repeating it, and allow others to call me out in order to put that claim to the test.

Faster apps are something that evolve. Most of the time, we don’t have the luxury of time necessary to support that evolution. I am fortunate enough to be in a position where I can keep revising my old code, making it more efficient, performant, and capable along the way. In this case, a one line change increased performance by at least an order of magnitude.

Previous
Previous

CAI Wrote a PostScript Interpreter

Next
Next

Two Years with a GPT