Now that our v1.6 release is out in the Marketplace, I wanted to take a minute to write an engineering blog on what it took to improve our dreaded finger lag problem: we’ve been battling it for quite a while, so I really hope that some of this content is helpful to other Windows Phone 7 developers out there. We LOVE this platform, and we love the phone, and anything we can do to help other developers not stumble on our mistakes is great, in our opinion.
Fundamentally, Wordament was struggling with two different problems: we had some code running on the UI thread that shouldn’t have, and we had a memory leak—a pretty severe one, actually. We were losing memory game-over-game in a really unclear way, but it was slowly, but surely killing our game’s performance in builds prior to 1.6. In fact, you could sometimes feel the lag start as early as game #3. We knew we had a problem, but we just didn’t have to tools to fix the problem.
Tip #1: If you are a developer and you want some insight into what your app is really doing: go and get the Windows Phone 7.1 (Mango) dev tools RIGHT NOW. Even if you aren’t shipping an app for Mango, these tools are going to help you find your problems in your 7.0 app (just make a copy of your project and upgrade that to 7.1 and the profile becomes available to you). The most important of these tools is the terrific new “Windows Phone Performance Analysis” app. We spent a bunch of time trying to figure out how to get all of our code “off the UI thread” and this tool does a great job of showing you where you are holding up the UI, and where your app is running poorly.
Tip #2: For Windows Phone 7.0 (RTM or NoDo): you need to periodically call the Garbage Collector, explicitly. We didn’t know until recently that the 7.0 release didn’t have a “generational garbage collector”, so to stay light-and-lean, you need to call GC.Collect often (we call it on a regular heartbeat now)—and that makes a really big difference to our working set. Even though we never really got too “fat” memory wise (we never get to the dreaded 90MB auto kill limit), it seems that once your app gets into the 35+ MB limit, the app starts to slow down. We understand that Mango comes with a better garbage collector, so perhaps this tip will no longer be necessary in the future.
Tip #3: Track what you create, track what you delete. This was a bit of a tough pill for us to swallow. C# has a garbage collector and that magically cleans up after you, right? Sort of. Aside from Tip 2 (above), you still have to be careful to explicitly clean up things if you want them to release. For Wordament, we now have a pretty elaborate “Heap tracking system” that watches every allocation and deallocation of objects we create (in Debug builds). We dump the total list in our debug spew every 10 – 15 seconds and watch that count like a hawk. For places where we needed to create Collections, like Lists and Dictionaries, we created our own, generic Debug subclass of those to make sure we are getting rid of those too. The most important little gotchas that we found are:
- Be very careful to unhook every event handler you hook in your Loaded event in your Unloaded event. Only hook Loaded and Unloaded inside your constructor. Every other event handler should be hooked up in Loaded and Unloaded so that page-to-page navigation cleans itself up. We found a bunch of cases where pages wouldn’t dispose if you had event handlers still hooked.
- Stop DispatcherTimers, unhook their Tick event, and set them to null! Timers keep pages around if they are enabled.
- If you use HttpWebRequest, be super vigilant in your error handling that you don’t leave a Stream or Response object open. Close all your streams! Network flakiness on cellphones is a thing. The MSDN sample here: http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse(VS.71).aspx is very good, but still has some lingering memory leaks in flakey network scenarios. We scratched our heads a bunch to find them all. Leaking network objects is bad!
- Be very careful with List databinding. Do not assume that list clean-up is automatic. If you trickle fill lists, be sure to explicitly clear them when you are done, or all of the item they reference can dangle around.
- Be very careful with Databinding in general. It’s beauty is its simplicity. Debugging it can be hell. We don’t use Databinding anymore.
- If you have to sit on a single page for a REALLY long time, consider using controls that you can create and destroy.
Tip #4: If fingering performance matters to you—you have to use Touch.FrameReported and avoid mouse events. This adds a lot of complexity, but the perf win is huge. In many cases, you will be doing your own hit testing (using Silverlight’s FindElementsInHostCoordinates method): here’s an example: http://blogs.microsoft.co.il/blogs/alex_golesh/archive/2009/08/11/silverlight-quick-tip-how-to-perform-a-hit-test.aspx
Tip #5: Don’t forward navigate… Pages are never deleted when you forward navigate, and are only sometimes deleted when you press BACK (in practice the phone seems to free pages that are two navigates away). Again, if you want it to appear like you are navigating forward, consider having one page with multiple view states and multiple controls that you create / destroy.
Tip #6: Try to never have more than one DispatcherTimer. There are instances where we need to have two (where the second is temporary), but you should try really hard to only have one.
Tip #7: Inline all of your DataTemplates! Silverlight has a list template memory leak, that causes whole lists to leak over time: http://forums.silverlight.net/t/171739.aspx (this bug repros on Windows Phone 7.0’s RTM and NoDo Silverlight).
We hope these tips help! We know we have been searching for stuff like this ourselves, so hopefully we can save you some time in the future. If you got here because you are a developer, and haven’t played Wordament, you can thank us by downloading our app and rating us. You might also find that you really like our game!
Thanks for listening,