Sgt. Conker We are "absolutely fine"

27Nov/094

Article : Multi-threading your XNA

by Catalin Zima

Contents:

1. Introduction

2. A Look at Multithreading Primitives

3. Multithreading Update/Draw

4. Example: Balls

5. Conclusions

6. Downloads

7. References

In this tutorial you will learn how to use multi-threading in your XNA games. The tutorial starts with a short introduction about multi-threading, the Xbox 360 architecture, and the advantages and disadvantages of using multi-threading for XNA games. This will be followed by a very brief look at the classes and primitives that we will use in the rest of the article. After this, the main part of this tutorial is focused on using multi-threading for the main game loop. You will learn how you can separate the code for drawing and updating you game and run the two tasks in parallel. There are other ways to use multi-threading in your games, but they will not be covered by this tutorial. As a closing word, we will draw some conclusions and look at future developments.

1. Introduction

As game developers, we always want our games to be better. We want better graphics, better physics, better A.I., and so on, just to make our customers happy. However, we always have so little time to do all of these. I'm not talking about implementation time, which is of little interest to our players, but about the running time. Players are really picky. They expect the games they play to run smooth and fluent, which means our games should run at 30 or even 60 FPS (Frames Per Second). This leaves us with 16.66 milliseconds in which we have to squeeze all the physics, graphics, gameplay and AI we want. For some games this is more than enough. For others, that is a painfully low threshold.

So what's this about multi-threading? Normally, your game starts and runs on a single thread, so all the tasks run sequentially one after the other. Thus, the total running time of a frame will be equal to the sum of the running times of each task done during that frame.

Using multiple threads would mean taking some of these tasks, and running them in parallel, at the same time as other tasks are running. In this configuration, the total running time of a frame will be roughly equal to the running time of the slowest of these parallel sets of tasks. Thus, the overall frame time is lower than when using a single thread, so the performance of the game is higher, yielding better framerates and smoother animations.

There are a few important things you need to remember after looking at this picture.

  • Not all tasks need to be parallelized. In this example, we left the input handling running serially. Maybe both game logic and animations need the input, or maybe you just feel better having a certain task run on its own, without anything else running in parallel. Just remember that it can be done, and in some cases you will probably want this to happen.
  • The tasks need to be independent from one another. As they are running in parallel, it's not that easy for them to communicate, so we need to have some mechanism to pass data between parallel tasks. Ideally, you would have completely independent tasks, but in a game, this is rarely possible. Animation needs physics and A.I. data, A.I. also needs physics, rendering needs information from all the other processes, and so on. We will look at this later in the article.
  • One of the most important things to remember it that the speed gained by using multi-threading is still limited by the speed of the slowest branch. In the example, even though we use two threads, the frame time is not quite half of the initial frame time. Even if we create a separate thread for each task, the running time of the frame will be at least the running time of the physics task (the slowest in our example). Also, communication and synchronization between threads introduces some overhead, which adds to the total frame time.

Now let's see where will we run these threads. In the latest years, PCs have evolved and are now equipped with processors having 2 or 4 cores. This means that we can have up to 4 threads running completely independent of one another, and not sharing processor time. Of course, no one's limiting you to one thread per core, but it's better to do so with computationally intensive tasks. The Xbox 360 has a different architecture. It is equipped with a special IBM processor with 3 cores, each of them capable or running two independent hardware threads. So in total, we have 6 hardware threads on the 360. Unfortunately, two of them are reserved, and cannot be used by us, as they are used by the XNA Framework, and other system tasks. But having 4 hardware threads is not that bad either. Below we can see what these threads are.

Hardware Thread Core Notes
0 0 Not Available. Reserved for the XNA Framework
1 0 Available.
2 1 Not Available. Reserved for the XNA Framework
3 1 Available.
4 2 Available.
5 2 Available.

So what are the advantages of using multi-threading for your games? The main advantages are greater performance, higher frame rates, possibility to add more complex simulations for physics, AI, or other things. The disadvantages are the added complexity needed to write your game for multiple threads. Many times, your game will run just fine on a single thread. Separating tasks and taking care of all issues associated with multi-threading is a complex task, and requires complex data structures and synchronization code and it may not be worth the effort. You need to have a good understanding of how threads and memory sharing works, or else your multi-threaded code might run even slower than your single-threaded code.
Another great minus is that multi-threaded code is very hard to debug. The errors that occur because of the interaction between threads are very difficult to replicate and identify, and thus, difficult to remove. With that said, let's go on and see what classes and keywords you need to understand in order to follow this tutorial.

About Sgt. Conker

The Sergeant!
Comments (4) Trackbacks (1)
  1. Hi Catalin, I originally went down a very similar research path as what you are describing in your threaded-rendering system.

    However I did some prototyping and came to the realization that the GPU is already running asynchronously, and only forces a CPU-side block if the previous frame hasnt finished when the next .Begin() is called. This result plus the added complexity of caching game-state led me to abandon this system.

    What we are planning to do on our engine is to use a seperate cpu thread to process vertex data (batch instancing), but that’s really no different than multithreading other cpu-side modules.

  2. Hi Jason.
    Yes, the GPU does indeed run asynchronously from the CPU. However, the speed of the GPU is rarely the main performance bottleneck. Most time, the problem is with the communication between the CPU and the GPU which happens at each DrawXXX() call. Having a high number of draw calls eats up lots of CPU time, and that is what I am trying to reduce in this article. So what I am threading is not actually the GPU-drawing, but the CPU invoking of draw calls (which happens at driver-level).
    In most multi-threading approaches, you either try to distribute the non-graphics CPU-work on multiple threads, or try to distribute the GPU-communication work done by the CPU.

    But yes, the approach is not what I’d recommend for a truly advanced system. It is good for learning purposes, and it is definitely a robust way to add threading to your game and obtain a good performance boost, but at the cost of extra memory needed for the two buffers.

    For a more complex threading system, there are some other ways to do it, as presented in some of the papers I linked to at the bottom of the article. Each method has it’s advantages, and you can’t always decide that one way is better then others. It usually depends on the structure of the rest of the engine.

  3. Nice one. It will help me a lot.

    Thanks,
    Timo

  4. yah, I think your system is a great intro to the very complex world of multithreading. Like I said, I originally went down a very similar path to what you are describing, so I do feel that there are benefits, but in my situation the drawbacks outweighed them.


Leave a comment