Multi-threading is often causing problems for multiple reasons: synchronization, deadlocks, concurrent access to memory… But with my recent experience with multi-threading in XNA/C#, you can really do simple and efficient optimizations.
While looking for possible performance improvements in Spring Up Harmony for Xbox 360 (PC), I have seen a simple situation really suited for multi-threading. You can see that situation with the following screenshot of my in-game profiler (click to enlarge):
I have two tasks with performance issues since I started to run the game on Xbox 360 : the dynamics of the background effect and the computation of the estimation of the launch of the ball (running a simplified physics simulation multiple times, through Box2D.XNA). On this screenshot, these tasks are called VelGrid.U(9) and Launchers.U(14). The first part of VelGrid.U (Comp.Balls,10) uses the data of the physics simulation but DynaColLines(11) doesn’t. Therefore, the VelGrid.DynaColLines and Launchers.U are totally independent with each others. No data is shared between these code sections. Perfect candidates for my initiation to multi-threading in XNA!
The e-book found here (recommended in multiple threading articles) is really awesome and helped me find the algorithm I need to use. I want to start the Launchers.U at the same time than VelGrid.DynaColLines and wait until both are finished. I know this is not optimal, but it allows me to simplify the problem and avoid so many threading pitfalls.
I just have to use two AutoResetEvents. The Launchers.U section is created in a thread and waits for a “Go” signal from VelGrid. After doing its job, it sets a Ready signal so that VelGrid knows when to carry on after doing its own stuff. Two different cases can happen, depending on which section finishes before the other.
Here are screenshots of the in-game profiler showing the differences (taken on X360). Every code section prefixed by a * means it’s executed on the thread.
Case 1 : The code on the main thread completed before the code on the worker thread:
Case 2 : the code on the main thread completed after the code on the worker thread:
The thread is idle while the main thread still works (red arrow).
With this simple threading mechanism (coded and tested in a few hours only), I have been able to optimize the game. In the basic test level shown here, instead of 2.3 + 3.1 = 5.4 milliseconds spent on the main thread for both features, the game now spends 3.6 milliseconds, running two threads at the same time (33% gain). In this test scene, the Velgrid.DynaColLines is “free”.
Since I took these screenshots, I have made a few more changes : now, the whole Box2D update used while the game is in motion is threaded too.
Last important thing to know, on Xbox 360, you must choose which core to use for your thread. Just read the msdn page about the SetProcessorAffinity, everything you need to know is here. You can see in this article that I still have a few cores available
In conclusion, if you have independent sections of code that take some time to run, you can improve that very quickly using this method.