I just spent endless hours in the last few days to find a very annoying performance bug in my iOS Unity project. I observed a continuous performance decrease from minute to minute when running the game in loop mode for testing. After about 10-20 minutes it was no fun anymore to play. The strange thing is: The effect occurs especially when the device is lying still on the table. When I move the device, frame rate rises noticeably but decreases again when there is no movement.
The iPhone game is written using Unity game engine and an Obj-C library to access Apple’s Core Motion framework. Performance is excellent on iPhone 5 and used to be pretty good on iPhone 4 in the past.
After a while I could exclude the C# part and focussed on the native code plugin. There were several things to check like the pure native code call itself, the fetching and buffering of data, heavy calculations and last not least the fact the specific design:
I use the ‚old style‘ pull approach to fetch CMDeviceMotion instances i.e. startDeviceMotionUpdates without block handler. The pulling code runs in a separate thread, which turned out to be the most perfomant way – Well, in the past.
Although it pretty much looked like a memory leak, Unity’s built-in iOS profiler (console) didn’t show significant memory consumption. I was not sure if native plugin code is considered in statistics, so I used the Xcode profiler. But this too didn’t show any leaks as far as I can see it, just a bulk of memory allocations and deallocs, which was a bit surprising to me (I think recycling CMDeviceMotion instances internally might be a bit faster when polling, but who cares it won’t be a big deal)
Finally I found the culprit: Calls to deviceMotionUpdateInterval or to be more precise changing the interval pretty often.
According to section Choosing a Motion Event Update Interval in Event Handling Guide for iOS a larger interval improves battery life. So I implemented some logic to slow down the frequency to 2Hz when the game is not active i.e. game over, pausing, main menu, … When resuming game play the frequency was set to 60 Hz again.
After commenting out the code for changing the frequency, the problem disappeared on all test devices.
This was working fine all the time. So the only reasonable explanation is the update to iOS 6.1.3 two months ago. It’s hard to determine the exact time in revision history when the problem has started, because I did all longer tests on the fancy iPhone 5 and just quick tests on iPhone 4. But it looks like there is no other change that colud have caused these issues.
If my assumption is right it appears to be a bug in Core Motion framework and I filed a bug report at Apple. What I have learned:
- Always test on all devices although it’s boring
- Choose the most defensive way of programming against external frameworks that choose from time to time and run on pretty different hardware