Stacking the Numbers

Jax has to handle a lot of low-level stuff. It comes part and parcel with the package: the whole point, the very purpose of Jax is to automate the basics so that you can get down to serious business. It comes as no surprise, then, that sometimes I have to actually code these things.

One of the most fundamental areas of Jax is the matrix stack. The matrix stack is very helpful because it provides a way to save and restore state throughout the rendering process, without having to rebuild the various matrices at every stage.

Today I made a fairly minor change to how the matrix stack manages its matrices. It was a small change that led to a big difference in performance!

When it was originally coded, the matrix stack would just recalculate the ModelView matrix whenever either the Model or View matrices were updated, and that was acceptable because the ModelView is central to the rendering process; but the stack evolved over time in order to support more complicated rendering tricks like normal mapping, shadow mapping, and so on. Eventually, the stack was maintaining numerous matrices (about a dozen of them!) that needed to be recalculated whenever some other matrix that they depended upon changed.

The change, then, was to simply not update some matrices whenever others changed. Instead, when the key matrices change, the matrix stack will simply flag its siblings as “tainted”, and recalculate them lazily — that is, only recalculate matrices when they are actually being used. What a concept!

I couldn’t resist doing some benchmarks after making this change, even though it was pretty much a given that performance would be better. Of course, the results are subjective: if, for example, you have an application which makes use of every matrix that the matrix stack provides, then you’re not going to see any improvement whatsoever; if, however, you have an application that only uses a few matrices, then you’re going to see a massive improvement. Most apps are somewhere in between these two extremes.

So, for my benchmark, I took a very common scenario:

  1. Push a new matrix onto the stack.
  2. Multiply a model matrix.
  3. Load a new view matrix.
  4. Retrieve the resultant model-view-projection matrix.
  5. Pop the new matrix off of the stack.
The results were nothing short of impressive!
Old matrix stack - 57,507 ops/sec ±0.58% (55 runs sampled)
New matrix stack - 1,217,220 ops/sec ±0.41% (61 runs sampled)

In other words, the new matrix stack proved to be over 21 times faster! Awesome!

It’s worth reiterating that this is only one particular use case; for example, loading a new projection matrix while leaving the view matrix unmodified yields “only” a 13x performance increase. The improvements will be completely dependent upon the application environment, but I think the vast majority of users will see a significant benefit — and that, in my book, spells “WIN.”

(Disclaimer: I’m not saying that all of Jax is about to get 2100% faster. I’m just saying that its matrix stack is getting faster. But yes, this will probably improve performance throughout Jax to one degree or another, in combination with numerous other optimizations I’ve been working on. I’ll benchmark the new version of Jax against v2.x in the coming weeks. Stay tuned…)

Comments are closed.