Analysing Performance in Unity3D iOS Apps

Analysing Performance in Unity3D iOS Apps

Doing game development for mobile devices often leads to situations where you feel like programming in the 1990s again. Unacceptable drops in frame rate, low memory warnings and other performance hassle could be very painful and cost you days and weeks.

So I am going to describe the basic techniques for performance analysis and how to use the profilers for Unity3D pro on iOS including some hints how to tweak them. A sample case detecting spontaneous frame rate drops is given at the end.

Problem Areas

I like to distinguish between 3 different types of performance problems:

  1. Frame rate is too low in general
    Feel lucky when there is just one cause but most often a couple of reasons is responsible in this case and fixing it is a longer-term problem. Typical factors are too many draw calls, high vertex counts, expensive code in Update methods, extensive logging, and many more. Profiling and testing with different assets have to reveal the major bottlenecks step by step.
  2. Frame rate drops in certain situations
    As the problem is reproducible, this appears to be the easiest type: „just“ profile and analyse.
  3. Sudden irreproducible drops of the frame rate from time to time
    Indicates either some activity in the background driven by the app like connecting to a server, waiting for a thread if using native code plugins, … or it is related to memory consumption. When the garbage collector starts freeing bigger amounts of memory this might needs 10 or more milliseconds and might occur at unpredictable times.

 

The Profiling Tools

There are two different profilers for Unity3D iOS projects, XCode Instruments, the stats window and HUD FPS display.

Built-In Profiler

This profiler write information to the XCode console. Setting up the profiler and the meaning of the output fileds is described pretty well in Measuring Performance with the Built-in Profiler. Just set #define ENABLE_INTERNAL_PROFILER 1 in Classes/iPhone_Profiler.h and your console shows periodically something like:

iOS Built-In Profiler

If you need to do some statistical analysis on large series of data it’s worth to tweak the console output for convenient pasting into a spreadsheet. First you might want to change the frequency from every 30 frames to a lower rate. In file iPhone_Profiler.cpp search for the line const int EachNthFrame = 30; and change it to whatever you need. If you need to change it more often, a preprocessor define in the header file is convenient for doing this on the fly.

Every single output consists of multiple lines. This is nice for getting a quick idea what’s going on it is not that spreadsheet compliant. So we remove the EOLs. To do so, again open iPhone_Profiler.cpp and look for the if block if (_frameId == EachNthFrame) containing all the printf_console statements to produce the output. Replace all \n by blanks except from the last printf call which produces a separator (remove the minus signs here but leave the \n to separate each profiling output from one another).

After running your app you can just copy the console output to a temporary file, let’s call this raw.log. Then open a terminal and enter:

grep „iPhone Unity internal profiler stats:“ raw.log | cut -c 39- > profiling.log

This removes all non-profiler lines and then within the remaining profiling lines the text „iPhone Unity internal profiler stats:“ at the beginning. Alternatively you can do that manually in the editor.

Open profiling.log select all, copy and paste it into OpenOffice using the following import settings:

Import in OpenOffice

(Excel users paste and perform a text to columns)

Now we are can do some average calculation and view how single values are changing over the time. If you have to do this quite often you might have a look at my OpenOffice performance calculation sheet (s. instructions sheet inside how to use). It references the pasted values from the raw data sheet in a result sheet. There it provides formatting and averages in summary line using indirect addressing as the lengths of profiling sessions are never constant.

Unity3D Profiler

The Unity3D profiler (available with iOS pro license only) can be used for the editor player and for the device. Connecting to the device is pretty straightforward, just enable Development Build and Autoconnect Profiler. It’s nice but most of the time I use it for profiling the app in the Unity editor player. The reason is that the profiler produces huge overhead and reaction time is pretty sluggish.

Using it in editor player is a very powerful tool for finding bottlenecks. Start with Deep Profiling disabled, order the sub-window showing the called methods for example by Time ms and check the frames around areas with high peaks. If you isolated suspicious scripts but have no concrete idea, you can turn on deep profiling (needs restart) to take a closer look at all methods called from the scripts to blame for. As we are just focussing on iPhone apps, profiling on a Mac shouldn’t affect FPS in editor player.

Sometimes it’s useful to profile a special section of your code especially when deep profiling gets too complex or slows down the performance. For this purpose you can instruct the profiler to gather information about this piece of code:

 

Will show up in profiler window as —-Test (1):

Samples

You can use several BeginSample / EndSample sections, even nesting is allowed, but note that this 1) doesn’t work with deep profiling enabled and 2) is considered on iPhone only in development builds.

FPS Display

It’s always a good idea to integrate some code in your app that shows the current FPS permanently during he development phase. Check out the FramesPerSecond from the Unify Community Wiki. You will find scripts for C#, Javascript and Boo to easily integrate a FPS counter in your app via a GUIText component.

XCode Instruments

Instruments is a powerful tool of course but I use it rarely for Unity iOS projects because most problems are related to scripts, vertex counts, draw calls, and so on. If I start the Time Profiler I just see hexadecimal addresses for my C# functions although it might have worked somewhere, somehow due to this forum entry.

Nevertheless there are some specific areas like real memory consumption and CPU load compared to other processes (Activity Monitor, Leaks, Allocations, … template), energy consumption, network traffic, etc. where instruments can give valuable information. For profiling of a native Obj-C code it’s of course the tool of your choice.

Statistics Window in Game View

For the sake of completeness the statistics window in game view can show some nice on the fly info. OK this one is trivial ;- ) as every beginner clicks on it after a few hours but to get representative information, two more steps are helpful:

  • Set Graphics Emulation to iPhone 4, 3GS, iPad OpenGL ES2.0 to ensure that the quality settings are the same as on iPhone; Otherwise editor player shows more draw calls, vertices, etc. than used when running on the device
  • Choose iPhone 4g Wide or whatever you prefer in game view as aspect ratio; This option is available only after building an iOS player

Example Case Memory Allocation Issues

In order to get an idea how to use the profilers I am going to show an example similar to the one I just have run into.

First of all I noticed a slight drop in frame rate and sometimes a jerky behaviour as if there is a tiny performance loss maybe a few frames. So the first choice is to enable the built-in profiler (output each 150th frame) and take a look at the values.

OpenOffice Max Collections

Although the frame rate was OK in average I recognised an abnormal behaviour regarding memory particularly in regard to the fact that the measured level was my test level without any expensive meshes or whatever. The garbage collector seem to be active every 200 – 300 frames.

Now you might think let’s connect the Unity profiler and check which scripts are wasting memory. Unfortunately this doesn’t work because iOS always reports 0 in the GC Alloc column. The way to go is using it with editor player in flat mode and see which scripts are allocating memory on a regular basis. Although running the app in editor player doesn’t show at all some performance hickups when garbage collection takes place it can give a hint about the memory consumer:

Unity Profiler GC Alloc

Next step is to use it in deep profile mode. The displayed column GC Alloc then shows allocations in the method itself only, but not what is going on in the methods called from there. Our method appears harmless:

Unity Profiler GC Alloc Deep Mode

Inspecting the call hierarchy reveals which method is to blame for:

Unity Profiler GC Alloc Deep Mode (2)

So a simple string compare when checking the type of physic material turned out to be the culprit:

 

Called at least threetimes during every FixedUpdate (80 calls/sec) triggered 80x3x60 = 14,400 bytes / sec. This implies 140 KB in 10 seconds. This can be reason enough to trigger additional garbage collection. Note that this is just a constructed sample while my original problem included some more actions and was located within a loop but I didn’t want to restore the bug 😉 So don’t worry for every simple string compare now but take care when they are located within loops or called very often on each Update.

Conclusion

Performance analysis is always painful and I hope this overview shed some light on it. In most cases it’s more than one problem that need to be fixed and a lot of trouble came from vertex counts, draw calls, texture compression, shaders, … Aside from the Unity documentation I found a Cinese blog post translated in English which is a pretty good list of do’s and don’ts: Optimizing Performance in iOS Part1:Optimizing Graphics Performance

 

Comments are closed.

Durch die weitere Nutzung der Webseite stimmen Sie der Verwendung von Cookies zu Mehr
By continuing to use the site, you agree to the use of cookies. More

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close