The subject is a reference to D&D 4th edition. A normal extended rest lasts about 6-8 hours. In my case, it was nearly two months!
But I have resumed. It turns out that optimizing code is fairly soul-crushing. The flipside of "Premature optimization is the root of all evil" is today's Programming Tip of the Day:
It's not premature optimization if it's actually time to optimize
But if you've been approaching development the entire time with the attitude that you don't need to optimize, it becomes quite an adjustment to go back to the problem parts and realize how horrible your code actually was.
I want to post this before midnight, so I'm not going to go into a lot of detail on the fixes. Most involved caching a value rather than recalculating it, something I know is faster but won't do unless the situation really calls for it, as I've been bitten in the ass before by out-of-date cache data. I replaced a linear search of an array that happened every frame with a dictionary lookup, which provided the expected results.
So on one hand, not optimizing early is a good idea. On the other hand, you have to pay for it later when the time comes. On the gripping hand, the code's so poor that I really had nowhere to go but up!
Showing posts with label profiling. Show all posts
Showing posts with label profiling. Show all posts
Saturday, August 16, 2008
Thursday, June 19, 2008
Day 159: Profiling
Today was my first in-depth profiling. Yesterday I did some looking over, but even with ruby-prof's excellent HTML output, it was taking me a while to get my head around exactly where the bottlenecks in my code were.
Then I found out that ruby-prof can output in a format that KCacheGrind understands. I also found out that KCacheGrind exists, which was likewise helpful, and suddenly I had a tool which could walk me through parts that needed optimizing.
My first surprise was that Set::merge was called FOUR MILLION TIMES. No, I'm not making that number up. When you ask a KuiObject for its attributes, children, etc, it collects all of the ones it knows about in a set and then merges it with the ones its superclass knows about. Except there was no caching here, it was doing it every single time.
Even that wouldn't have been a problem, because you'd think I wouldn't be asking what every single attribute of an object is except when I'm loading/saving (and then time is not as important). But it turns out that it was heavily used in comparison. Two KuiObjects are == if all their children, attributes, etc, are equal, and this requires me to call Set::merge above.
So I fixed the caching problem, and that sped things up. Then I made it so that two KuiObjects are == if their tags are ==. This changed a dozen+ string comparisons and countless object comparisons into one string comparison. That sped things up a lot.
The beauty of using profiling is that I can tell whether harebrained ideas I have for optimization actually work. For instance, I thought that the string comparisons were likely to also be too slow, and I figured symbol comparisons would be faster. Turns out, no, they're not, converting the strings to symbols and comparing them is a little bit slower than just comparing the strings to begin with.
My other optimization idea, to take a line of the form (angle / 10).to_i and change it to (angle.to_i / 10), did speed things up a bit, due to the fortunate elimination of floating point division.
At this point the game is smooth again in sectors with lots of ships, but not when they start shooting the hell out of someone. There are a few other spots in the code I can optimize, fortunately. I would like to avoid having to drop down to C, as I know that this more likely indicates a failure to optimize on my part than it does a shortcoming on Ruby's part.
Then I found out that ruby-prof can output in a format that KCacheGrind understands. I also found out that KCacheGrind exists, which was likewise helpful, and suddenly I had a tool which could walk me through parts that needed optimizing.
My first surprise was that Set::merge was called FOUR MILLION TIMES. No, I'm not making that number up. When you ask a KuiObject for its attributes, children, etc, it collects all of the ones it knows about in a set and then merges it with the ones its superclass knows about. Except there was no caching here, it was doing it every single time.
Even that wouldn't have been a problem, because you'd think I wouldn't be asking what every single attribute of an object is except when I'm loading/saving (and then time is not as important). But it turns out that it was heavily used in comparison. Two KuiObjects are == if all their children, attributes, etc, are equal, and this requires me to call Set::merge above.
So I fixed the caching problem, and that sped things up. Then I made it so that two KuiObjects are == if their tags are ==. This changed a dozen+ string comparisons and countless object comparisons into one string comparison. That sped things up a lot.
The beauty of using profiling is that I can tell whether harebrained ideas I have for optimization actually work. For instance, I thought that the string comparisons were likely to also be too slow, and I figured symbol comparisons would be faster. Turns out, no, they're not, converting the strings to symbols and comparing them is a little bit slower than just comparing the strings to begin with.
My other optimization idea, to take a line of the form (angle / 10).to_i and change it to (angle.to_i / 10), did speed things up a bit, due to the fortunate elimination of floating point division.
At this point the game is smooth again in sectors with lots of ships, but not when they start shooting the hell out of someone. There are a few other spots in the code I can optimize, fortunately. I would like to avoid having to drop down to C, as I know that this more likely indicates a failure to optimize on my part than it does a shortcoming on Ruby's part.
Day 158: Reno
I'm currently attending the Smalltalk Solutions conference in Reno, where we sent a man the #kill message, just to see if he understood.
Trust me, if you know Smalltalk, that's a very funny joke.
Good news/bad news on the development front. The good news is that I finished the two tickets I mentioned: #179 was for making lasers that weren't slower than the ships that fired them, and #180 was all about adding "scenery" ships to the tutorial.
That's where I ran into the bad news. The first problem was that adding weapons to the ship in the editor still wasn't working, despite all the testing I'd done. What happened on the back end is that any time you give an object a tag, if it had a tag before it deletes it from the repository and re-registers with the new tag. Whenever I created a new weapon for a ship, it replaced the original! So if I had 2 lasers on board, suddenly the object in the repository would be those same two lasers. It was a mess to track down, but thankfully a relatively easy fix.
The worse news was that it's slow! There are noticeable hiccups in even sectors where there's no combat. The traditional profiling approach of using 'ruby -r profile' had so much overhead I couldn't actually run the game. Thankfully, Ruby-prof exists. It's got a small enough overhead that I can actually play my game.
I have a lot of data to go over for optimizing, and I'm not releasing the tutorial until I'm done, so I have one more ticket to close, #96, which I have aptly titled "OH GOD MY CPU".
For those interested in my contest entry, I am temporarily hosting it on my own machine, so get it while it's still up! I think the contest turned out really well and I'm happy that my entry ended up as good as it was.
Trust me, if you know Smalltalk, that's a very funny joke.
Good news/bad news on the development front. The good news is that I finished the two tickets I mentioned: #179 was for making lasers that weren't slower than the ships that fired them, and #180 was all about adding "scenery" ships to the tutorial.
That's where I ran into the bad news. The first problem was that adding weapons to the ship in the editor still wasn't working, despite all the testing I'd done. What happened on the back end is that any time you give an object a tag, if it had a tag before it deletes it from the repository and re-registers with the new tag. Whenever I created a new weapon for a ship, it replaced the original! So if I had 2 lasers on board, suddenly the object in the repository would be those same two lasers. It was a mess to track down, but thankfully a relatively easy fix.
The worse news was that it's slow! There are noticeable hiccups in even sectors where there's no combat. The traditional profiling approach of using 'ruby -r profile' had so much overhead I couldn't actually run the game. Thankfully, Ruby-prof exists. It's got a small enough overhead that I can actually play my game.
I have a lot of data to go over for optimizing, and I'm not releasing the tutorial until I'm done, so I have one more ticket to close, #96, which I have aptly titled "OH GOD MY CPU".
For those interested in my contest entry, I am temporarily hosting it on my own machine, so get it while it's still up! I think the contest turned out really well and I'm happy that my entry ended up as good as it was.
Labels:
contest,
profiling,
smalltalksolutions,
tutorial
Subscribe to:
Posts (Atom)
