Some Optimization Tips

Forums Programming cocos2d support (graphics engine) Some Optimization Tips

This topic contains 27 replies, has 15 voices, and was last updated by  Tim Knauf 2 years, 8 months ago.

Viewing 25 posts - 1 through 25 (of 28 total)
Author Posts
Author Posts
February 20, 2011 at 7:34 pm #228667

slhertz
Participant
@slhertz

Cocos2d: we all love it.

Objective-c: it’s more of a love hate relationship

Objective-c is a powerful language due to its dynamic nature, but that comes at a cost. The overhead of calling a function is significantly higher than calling the equivalent function in plain old C. Most of the time, we accept this overhead because it allows our code to be more dynamic, but in the case of an ever-running game loop (like we all use), it may be better to eliminate. To summarize the overhead, check out this link:

Here I will discuss two nice optimizations:

1. Avoid sending messages to objects!

That is, avoid using [object message] to call functions. It is extremely costly. Instead we can play a clever trick and use function pointers. Consider the following code:

-(id) function:(id)arg0 arg1:(id)arg1 arg2:(id)arg2;

First we declare a selector which refers to this function:

SEL selFunction = @selector(function:arg1:arg2:);

Then we create a function pointer:

id (*impFunction)(id target, SEL selector, id arg0, id arg1, id arg2) = (id (*)(id, SEL, id, id, id)[target methodForSelector:selFunction];

The return value of methodForSelector: must be casted correctly! Now the function can be called as follows:

impFunction(target, selFunction, arg0, arg1, arg2);

This will behave identically to the original objective-c function, but will be much faster. Try giving your classes a set of function pointers as member variables and initializing them in your classes’ init methods! Then they can be called from anywhere! This optimization is especially important for functions called inside of your game loop! Next, a related semi-related optimization:

2. Don’t read properties with object.property!

Since all of your objects are inherently pointers, try:

object->property;

Note that in cocos2d most properties are protected. This can be fixed by just placing an @public before all the properties are declared in the @interface file. In particular, I did this for CCNode and now I can read properties as above without any complaints from the compiler. Also note that most properties are declared as

node->position_;

with an underscore afterward. So for most cocos2d properties,

node->position;

will not work. Be careful if you set properties in this manner!. Due to the object oriented nature of cocos2d, setting properties this way will bypass calls to the superclass’ setter method, producing strange behavior.

These are some great optimizations, and I have even considered modifying the cocos2d source in costly locations to use them. Of course, I haven’t looked too deep into the code, so they could already be there. I don’t have any statistics, but used properly, the methods above can double performance (at least from what I’ve read). Feel free to post some performance statistics!

February 20, 2011 at 7:52 pm #316444

David994A
Participant
@david994a

@slhertz Great Tips! I was considering replacing some Objective C methods with C Functions – I’m going to give your tips a try.

February 20, 2011 at 8:16 pm #316445

araker
Moderator
@araker

@slhertz, methode one is already used in places where it matters, like iterating through a large collection of items. That’s also the only place where it makes sense to use it, for methods in your game loop that are only called a couple of times the performance benefit would be negligible. I agree with the opinions about optimization stated in this topic.

February 20, 2011 at 9:28 pm #316446

slhertz
Participant
@slhertz

@araker nice post. Like I had said, I wasn’t sure if this optimization had already been made in the Cocos2d code. I haven’t taken a nice long look through it. Thanks for letting me know.

I agree that in many cases it will be negligible. I don’t expect people to go through their code and change every single function call to use this methodology (especially since it looks ugly :P).

However, there is another side. For a cached function, sending an objective-c message incurs an overhead of about 30 assembly instructions in the average case. Consider this compared to a regular c call which incurs an overhead of about 5 assembly instructions in the average case. In other words, c function calls are 6 times faster on average! This doesn’t even take into account the cases where the objective-c function isn’t already cached.

Here is the link I had meant to post originally (don’t know why it didn’t post):

http://www.mulle-kybernetik.com/artikel/Optimization/opti-3.html

Refer to this if you need more details.

The point is, as your posted link explains, that if your functions are slow to begin with, then yes, this won’t really matter because you should already be optimizing elsewhere. But if you are fairly confident that your code is already close to optimal, this can give you a nice little boost in areas where objective-c messages are sent over and over. Consider if you break your game loop into three objective-c functions, each of which call three more functions. If your game loop is running sixty times per second, we are looking at:

25*9*60 = 13500

extra assembly instructions per second that can be gracefully avoided. This doesn’t even take into account if there is a loop inside your game loop which is calling an objective c function over and over. Consider if your game loop has a loop that iterates over an NSArray with 50 items. Each call to [NSArray objectAtIndex:] has 25 instructions of overhead:

25*50*60 = 75000

extra assembly instructions per second. Now you may argue that objectAtIndex can be avoided (which it can), but what if you are calling some other function inside that loop that cannot be avoided?

I’m just saying that there could be a nice potential performance boost without sacrificing too much readability if used correctly

Don’t even get me started on the power of inlining :P

February 20, 2011 at 10:43 pm #316447

Steffen Altwiese
Moderator
@steve-oldmeadow

@slhertz – you can always use cocos2d-x if you want and then you can write everything in C++.

In practice these optimisations make little difference. Most people that suffer bad performance are stalling in the graphics pipeline.

February 20, 2011 at 11:16 pm #316448

Birkemose
Keymaster
@birkemose

@slhertz

75000 instructions will take less than 200uS on an old 3G. On anything newer, it’s just like ZAP! and it’s over.

I wouldn’t waste time on that.

February 20, 2011 at 11:43 pm #316449

hactar
Moderator
@hactar

Optimization such as this can be useful. I initially wrote my pathfinding algorithm in plain objective C. It worked, however with 200 enemies calculating their path at the beginning of the round there was a 5 second pause while the iphone (3g) was churning away. I then did some high level optimizations (e.g. dont bother calculating a path longer than X, calculate a path thats ends up close enough instead of exactly at the destination). This got the pause down to 1 second. I then ran instruments and noticed a lot of time was being spent messaging. So I rewrote the code in c (a similar step to what slhertz is suggesting) and presto, problem solved, no more delay.

So, as the others said, only optimize when you know what the issue is and only if you need to. Use instruments to profile your code (theres lots of video guides by apple on this in the developer section) and so to determine where the iphone is spending most of its time in, then optimize that code. Run instruments again, see what the new “highest time consumer” is and optimize that. Repeat until you’re happy with performance, stop.

Else you’ll spend hours optimizing parts of your code that have nothing to do with your bad performance.

February 21, 2011 at 2:40 am #316450

timTheMystic
@timthemystic

Found a performance boost. In the director’s mainloop it applies the orientation of the screen on each iteration. So instead of using three opengl calls to transform and rotate in place, you can get by with just two. You need to test with the ipad’s dimensions, I’ve only tested in the iphone simulator.

case CCDeviceOrientationLandscapeLeft:
// glTranslatef(w,h,0);
// glRotatef(-90,0,0,1);
// glTranslatef(-h,-w,0);

glRotatef(270.0f, 0.0f, 0.0f, 1.0f);
glTranslatef(-s.height, 0.0f, 0.0f); // glTranslatef(-480.0f, 0.0f, 0.0f);
break;

February 21, 2011 at 3:52 am #316451

timTheMystic
@timthemystic

Oh well, the above appears to be negligible on a device. What I found helps is to compile for -O3 rather than -Os. Runs consistently around 60 fps with box2d bodies and animation in the scene.

February 21, 2011 at 4:31 am #316452

spadict
Participant
@spadict

I wish I could add more to this thread about optimization but I’m probably the worst about it coming from PHP :S All very interesting to read, I love learning about these things.

Going forward, I would like to use a lot more C in my games. I feel like I’m missing out on a lot by only operating mentally on an Obj-C level.

February 21, 2011 at 7:12 am #316453

Benihana
Participant
@benihana

If your understanding of these optimizations is limited to the article, I’d be wary of spending the time to switch anything without running some speed tests. The article(s) is from sometime between 2000 and 2004, and before Objective-C 2.0 was introduced which contains some “runtime performance improvements” which may take into account the optimizations the article recommends.

I don’t know, but I guess my point is that they may be a waste of time. It would seem odd if Apple hadn’t considered these optimizations.

Your numbers are also a bit disingenuous, not to mention out of context (since an iPhone is somewhere in the 1000+ MIPS range, 75,000 instructions per second is less than 0.01%?).

February 21, 2011 at 10:35 am #316454

TBBle
@tbble

Note that method two is not reading properties, it’s reading the ivars behind them. So you bypass your setters, getters, KVO and protections therein. And if you try it for a property without a backing var (ie. @dynamic rather than @synthesize) it won’t compile, because the value doesn’t exist.

It also won’t work for properties defined in categories, I expect… But I don’t know for sure.

I’d suggest thinking really really carefully about doing stuff like that. Then choose not to do it. If you really really want to do it, don’t do it. If your case is a special exception to the preceeding rules, don’t do it. Of course, it’s possible I’ve said to you at some point elsewhere to go ahead and do it, you’ve found a particularly important exception. Don’t listen to me there, I’m wrong. Don’t do it.

^_^

If you want stuff like that, use an actual struct. As hactar has observed, C is available to you still, and you can gain speed by giving up what Objective-C gains you functionality-wise. But there’s no need to bypass the Objective-C runtime to get to the C, use C for what you want it for, and Objective-C for what you want it for.

Edit: Objective-C 2.0 text regarding IMP etc: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html#//apple_ref/doc/uid/TP40008048-CH104 (See also NSObject’s documentation)

February 21, 2011 at 12:05 pm #316455

itlgames
Moderator
@itlgames

@slhertz, great stuff thanks, option 1) I think is a good improvement to my game loop, as I was already seeing lot of time spent on the object messages, but had no clue how to improve. Note my bottleneck is the logic, not the graphics, so any small performance boost like this I can really feel the difference, I will be doing more tests and post here, but so far I can see an improvement on my FPS.

We also did post a few more tips time ago:

http://www.cocos2d-iphone.org/forum/topic/9910

I agree most of these are completely negligible in most cases, but if you run in a loop called 60 times per second, it matters. In gaming when we need decent FPS, any improvement like those are great.

February 21, 2011 at 12:32 pm #316456

Steffen Altwiese
Moderator
@steve-oldmeadow

Note my bottleneck is the logic, not the graphics

@itlgames – as has been said above, then you are probably better off using C or C++ rather than bastardising Objective C. That will also give you the benefit of being portable to other platforms.

February 21, 2011 at 1:00 pm #316457

itlgames
Moderator
@itlgames

you are probably better off using C or C++ rather than bastardising Objective C

To be honest is not that that bad, I can feel a considerable FPS drop on extreme situations, like lot of sprites moving around, that situation is what I use to test performance, but in reality I never have such amount of sprites on screen. Just to put some lights on my bottleneck, is the isometric reordering, it’s easy when all your sprites are in same map height level, but when you play with different heights it gets complicated, to the level is not really possible to do a perfect reordering (not using 2d sprites), but this is just another discussion.

February 21, 2011 at 3:28 pm #316458

pats
@pats

Great post and perfect timing.

Thanks

PatS

February 21, 2011 at 6:07 pm #316459

slhertz
Participant
@slhertz

@Benihana – This is a feature of the language. The optimizations you speak of are caching the messages, which I pointed out still incurs overhead. As mentioned, this overhead is significant in only specific places. I would not recommend going through your code and converting everything to a function pointer. Yes, the numbers I’ve provided are still insignificant, but they are simple examples. I promise you that there are situations where this overhead can become very large, and it would make sense to make these optimizations. Apple’s official article posted by TBBle points out this very fact. Because we are developing demanding software (games) on relatively limited hardware, these simple changes to our code can be justified.

@TBBle – Nice point. I think number two is definitely inherently risky, but its really nice for writing inline functions :P

@Steve Oldmeadow – It’s not really bastardizing Objective-C. Messages do this anyway. Refer to Apple’s documentation posted by TBBle. This optimization does the following: instead of looking up the same function multiple times, the function is looked up once and a pointer to it is stored so that it may be called directly.

February 21, 2011 at 6:42 pm #316460

abitofcode
Moderator
@abitofcode

While we’re on optimisation an explanation as to why every so often adding my sprites as a child of an empty node rather than directly to the Layer brings me back up from 45fps to 60fps would be appreciated. It’s the first thing I try when my fps drops to 45 and it typically works 60% (ish) of the time :s

February 21, 2011 at 10:39 pm #316461

TBBle
@tbble

@abitofcode: That’s something you’ll have to ask your instrumentation about, I suspect.

@slhertz: I think the bastardisation people are talking about is method 2, not method 1. As has been mentioned cocos2d itself uses method 1 for CCNode child method dispatch, so objecting to it on the cocos2d forums would be a little surprising.

February 22, 2011 at 1:28 am #316462

slhertz
Participant
@slhertz

@TBBle – Yea I would definitely put number two under the bastardization category.

February 22, 2011 at 1:47 am #316463

Steffen Altwiese
Moderator
@steve-oldmeadow

Yes, number 2 was what I was referring to as bastardisation. Modifying CCNode to make all the properties public is one of the worst pieces of advice I’ve ever seen. If you code like this you are relying on the internal implementation of cocos2d but this can change. The only thing you should rely on is the interface defined in the header file. Riq should be free to change the implementation of properties. Just because a property is using a synthesised get/set in this version of cocos2d does not mean it will be true in future versions.

I actually use technique 2 but only in the situation where I have two or more classes that would be friends in C++ terminology and where I have complete control over the implementation.

February 22, 2011 at 5:23 am #316464

slhertz
Participant
@slhertz

Like I said, I never use it to modify properties (unless they are properties I’ve created), only to read them. Not to mention the properties I am reading are position_ and contentSize_. I would be surprised if their implementation changed in the future to the point where their getter was doing more than reading a value.

February 22, 2011 at 5:52 am #316465

Steffen Altwiese
Moderator
@steve-oldmeadow

@slhertz – You have to appreciate there are a lot of very inexperienced programmers using cocos2d and also lots of people who don’t speak english as their first language. This is how lots of people will read what you wrote:

This can be fixed by just placing an @public before all the properties are declared in the @interface file. In particular, I did this for CCNode …

the methods above can double performance

If you are only accessing two properties why did you suggest to make them all public? I’m only concerned about the sh*t storm of support requests this is going to stir up. You can also bet when someone posts about the weird issue they are having they will totally forget to mention the fact that they hacked the cocos2d code and are now accessing all the properties directly.

February 22, 2011 at 2:31 pm #316466

slhertz
Participant
@slhertz

Whoa no need to get angry. I’m sorry I didn’t mean to cause trouble. If you are concerned about my post, remove it.

February 22, 2011 at 11:02 pm #316467

TBBle
@tbble

@slhertz: position_ and contentSize_ are two that are totally candidates to surprise you with setter/getters. Remember the pixels to points transition in 0.99.5? ^_^

@Steve Oldmeadow: Please don’t remove this post. I think it’s important that ideas like this are documented and discussed, rather than being reinvented privately. Not that I expect that was going to be your reaction…

@slhertz: No one’s angry. People’re firmly responding. It helps if you read everyone else’s forum posts at your own speaking speed, not your own reading speed, it makes everyone sound much less excitable. (I find, anyway)

Viewing 25 posts - 1 through 25 (of 28 total)

You must be logged in to reply to this topic.