on May 15, 2012
So let’s talk about data structures. I’ve mentioned way back at the start of the project that there are certain costs to using an octree. An octree will make interfacing with ALL blocks a hundred times faster but make dealing with a specific block several times slower. I’m curious how…
Hang on. My program has been behaving oddly recently. It’s like it will suddenly stop building new blocks and I’ll end up stuck on this island floating in empty space. I’ve got the program set to aim for 60fps, and if one thing starts eating too much CPU then the block-building gets choked off. Let’s see where these CPU cycles are going.
I add a little benchmarking loop. Right now there are just five systems running:
- Avatar: This moves the camera around and does a little super-cheap collision detection.
- World: During block generation, world allocates these tables of handy values to speed up building. This part just looks for out-of-use tables and unloads them.
- Scene: This is the heavy hitter. It does those crazy heavy-duty noise calculations, places the blocks, maintains the octree, and turns the blocks into textured polygons.
- Window: This bit really does almost nothing. It checks for keypresses.
- Qt: Ah. This is where Qt gets a chance to process stuff. Qt is the platform I’m using to write this. Go back to part 3 if you need a refresher.
Let’s see where the time is going:
I don’t have access to a microsecond timer here, so we have to make due with milliseconds. This is like wanting to measure how long a man’s stride is when he walks, but your ruler is only marked off in terms of kilometers. You have to measure a lot of individual steps to figure it out. These measurements were taken over the course of a second. There’s a thousand milliseconds in a second, so we’re looking to account for those thousand milliseconds. Note that there’s a tiny bit of overhead to taking these measurements and we’re likely to miss a tick-tock here or there on really short tasks, but this is good enough to give us a broad understanding of where the time is going.
Note that the item “Qt & Rendering” is there because I do my rendering during the bit where Qt gets a timeslice. The two items in parenthesis break that number down. (Rendering) is how much time I spend shoving polygons at the graphics card, and (Qt) is how much time Qt eats doing… whatever it is that Qt does.
This is not good. In fact, this is exactly what I was afraid of back when I started using Qt. Over half of my CPU cycles are being eaten by Qt, doing… what? I have no idea. Qt is in charge of I/O, so it’s doing some keyboard and mouse processing. But that stuff is so fast that I shouldn’t be able to measure it with this clock. It’s also in charge of drawing that black box with the checkboxes and text in it.
Could that be it? Is Qt burning six tenths of a second on a rectangle of text with a couple of checkboxes? Hm. If I get rid of the box then I won’t be able to see these results. Let’s get rid of the checkboxes and see if that changes anything.
That made a big difference. Qt is now “only” using half of the CPU to draw this stupid black rectangle and text. For contrast, during the other half of the second I’m rendering millions and millions of polygons. Picture two guys in a library. In the same hour, one of them reads the complete three-volume set of The Baroque Cycle and the other guy reads a single Family Circus strip.
Let’s do another test. Let’s give Qt a bunch more controls and see how it reacts.
I’ve added a little text label, another text box, and a couple of progress bars. (I dunno. They look kind of like health bars or something. Seems like a reasonable thing to expect if this was a game environment.) And now Qt is eating 71% of our CPU. This would be funny if it wasn’t so sad. This makes it pretty clear why nobody’s ever tried to use this thing for a game. A real game interface would be even more complex than this.
Keep in mind that these controls aren’t changing. I’m not altering the position of the faux-health bars or anything else that would create a need for them to be re-drawn every frame. The text boxes only update once a second.
With performance like this, I might as well go use Visual Basic. (No, not really.) Note that this doesn’t mean that Qt is bad. It’s just built with different goals. Getting a cross-platform windowing system to play nice with 3D rendering like this requires a lot of levels of abstraction. A normal Qt application is just some kind of interface with buttons and sliders and whatnot, without any 3D rendering going on. In those circumstances, the user isn’t going to notice a few missing milliseconds. My program is sensitive to single-digit millisecond usage, but a human being generally isn’t going to notice until it’s in the hundreds, and they probably won’t care until it’s near a thousand. The performance needs of Qt are at least two orders of magnitude from the performance needs of a 3D game.
I find this page, which seems to be from one of the developers behind Qt. It confirms my worst fears: This CPU cost is an inescapable reality of using Qt. Even if all of those optimization techniques worked for me, and even if they applied to every little interface item, and even if I made maximum gains from all of them, it still wouldn’t do more than cut the CPU usage in half, which would still be ten times more than it should be.
If I disable the Qt drawing entirely (and have it print out the timing info to the console window instead) then we get:
Qt ms per Frame: 1
So the real overhead of using Qt is only ~1ms per frame. That’s reasonable. It’s just that the Qt drawing tools are too slow to be useful. A shame, really. “A platform-independent interface” was the main selling point of Qt for me. I’ve found a lot of other things to like about it since then, but losing the GUI is pretty much a deal-breaker.
When my processing began choking off I wanted to come in here and look for ways to optimize the octree or something. But it looks like the first order of business is “stop using qt” if I care about speed. I had a bunch of ideas for how I might tackle the crazy challenges that Goodfellow faced in part 32 of his series. Seems sort of pointless now. There’s no reason to agonize over the aerodynamics of your car while you’ve got a half-ton of cinderblocks in the back seat.
Oh sure, I could work on my optimizations like this, but the CPU drain of Qt is noisy, so measuring performance would be like trying to play Jenga on horseback. Also, experience has taught me that trying to monitor performance by reading a continuous spew of text in a console window is really aggravating.
I suppose I could take my code and go back to Visual Studio and SDL, which is what Project Frontier used. But that means shopping for a suite of image loading, interface-drawing, font-reading tools. Yuck. I don’t want to unravel some idiotic chain of dependencies. I don’t want to download a dozen different SDK‘s, spew their files all over my hard drive and then try to get them to compile and link with my project. I don’t want to have to choose between the package that only solves half the problem, the package that sucks to use, or the package that ties me to Windows. I don’t want to have to learn a new programming language.
I just – this external packages stuff is such a dang killjoy for me. I really, really, really hate it. It takes all the fun out of programming. When I was younger I tolerated it, but now I seem to have lost my patience. I know why this problem exists and I understand why there aren’t easy solutions. I just don’t have the desire to put up with it these days.
Maybe I’ll work a bit more on this project before I shelve it. Maybe I’ll migrate back over to Visual Studio and just muddle along with no interface. Maybe I’ll just stop here. I don’t know. I’m going to walk away from it for a bit and see how I feel about it then.
EDIT: I’ve been meaning to ask: For those of you who played around with Project Frontier, what was the biggest hassle in porting it? I know the capitalized filenames were a problem. “Main.cpp” instead of “main.cpp”. I was obliged to use the former professionally for years, and eventually it became a habit that I didn’t question. I’ve since been making sure everything is lowercase, but what other headaches did you encounter? (Aside from, you know, bugs and stuff.)
Shamus Young is an old-school OpenGL programmer, author, and composer. He runs this site and if anything is broken you should probably blame him.