Profiling Really Matters

The shot above shows a snapshot of the profiler results from a recent test I ran on the iPhone 4. Looking at it, you can see that within the update method, two of the most used methods are physics and positioning, which make sense for game. But it wasn’t always that way. The third most used, setViewpointCenter, once was second and all it did was adjust the view as the hero walked.

It was over a third more expensive to call than it is now.

Geez…

Why was this the case? Two things, object creation and querying.

To a lot of programmers, especially if you work in the web space, which I have, the thought of creating a string seems like a non-event. They’re not serious CPU hogs, so… whatever. But when your code has to execute under 16 milliseconds, sixty times a second, a string, especially repeated strings, become a real issue. A real issue.

By turning my strings into constants, I shaved off one third of the execution time because I wasn’t creating any new objects. One third!

That’s an optimization worth making!

The second thing that was happening was that I was querying the layers asking for the parallax layers so I could update them, each time. This is a lot of array looping, not to mention object creation (for the array objects themselves). Rather than query them each time, I moved the query to the initialization method and assigned them to instance variables.

This optimization shaved off the other two-thirds of the bottleneck I was experiencing.

Here’s the relevant code in actual method itself, what it used to look like:


void GameLayer::setViewpointCenter(CCPoint position) {
    /* ... */
    CCNode *parallax = this->loader->parallaxNodeWithUniqueName("foregroundParallax");
	if (parallax && (this->hero->getLevel() == BIT_16 || this->hero->getLevel() == BIT_32)) {
		parallax->setPositionX(parallax->getPositionX() + deltaPos.x);
		parallax->setPositionY(parallax->getPositionY() + deltaPos.y);
	}

    parallax = this->loader->parallaxNodeWithUniqueName("backgroundParallax");
	if (parallax) {
		parallax->setPositionX(parallax->getPositionX() + deltaPos.x);
		parallax->setPositionY(parallax->getPositionY() + deltaPos.y);
	}
    /* ... */
}

And here’s what it looks like now:


void GameLayer::setViewpointCenter(CCPoint position) {
       /* ... */
	if (this->forgroundParallax && (this->hero->getLevel() == BIT_16 || this->hero->getLevel() == BIT_32)) {
		this->forgroundParallax->setPositionX(this->forgroundParallax->getPositionX() + deltaPos.x);
		this->forgroundParallax->setPositionY(this->forgroundParallax->getPositionY() + deltaPos.y);
	}

	if (this->backgroundParallax) {
		this->backgroundParallax->setPositionX(this->backgroundParallax->getPositionX() + deltaPos.x);
		this->backgroundParallax->setPositionY(this->backgroundParallax->getPositionY() + deltaPos.y);
	}
       /* ... */
}

The end result? What had been keeping the frame rate on the iPhone 4 around 48-50 frames a second increased to a steady 58-60 frames per second, our goal.

Unfortunately, this doesn’t leave us with much headroom for additional code. But it does at least make it performant as written.

They say not to optimise as you’re writing a new piece of software but, in this case, I have to disagree with the idea of waiting for problems to crop up before doing this kind of work. By finding and fixing bottlenecks as I go, it gives me the flexibility to ensure that there’s room for the other things the main game loop needs to execute.

So… lessons learned?

  1. Change key strings to constants to avoid needless object creation.
  2. Change repeated querying for objects in the loop to a single query with the result stored in weak reference instance variables.

Basically, it boils down to avoid repeating yourself whenever necessary.