[In his own inimitably amusing fashion, Goo! programmer Tommy Refenes tackles the serious subject of creating and managing efficient and effective multi-threaded relationships, in this sponsored feature that's part of the Intel Visual Computing section.]
I'm not a father, but if I were, I think I would dread two
things: telling my kids about death and telling them
about the birds and the bees. However, the chances of
my becoming a father are growing slimmer each day
because, as a programmer, I spend about 90% of my
life in front of a computer.
But, hey, that's not to say I
haven't had any relationships or don't know a bad one
when I see it. I also know, based on my experience, what
makes a bad multi-threading relationship. (See what I
just did there? I just segued from bad relationships to
bad threading. Wait, what's that sound? That sounds
like every woman on earth running away from me…
Anyway, I may never be a father, but since I do have
some knowledge on threading practices, I'm going to
tell you about the latter. By the end of this article I hope
you will be able to create a threading relationship with
your processor and game that leaves you feeling fulfilled,
instead of feeling so cold and empty all the time. So
have a seat and let me tell you all about the complex
relationship between threads and the programmers who
Starting a Good Relationship
Good relationships are wonderful; bad relationships
make you question everything about yourself and cause
your world to crumble. When you first meet someone,
everything is new and shiny and wonderful, and it seems
as if the sky is the limit for your happiness.
Well, it's the same thing with threading. When you first
hear about this sexy little thing called "multi-threaded
programming" your mind starts racing. "Oh, I can thread
this," you think. "Oh, I can do this on another thread"
or "Oh, I can speed this up over two threads." The sky's
the limit, right?
In a way, yes it is, but how do you know
which code will benefit from threading, and which code
won't? Fortunately there are signs, just as in a human
relationship, that will tell you, "She's good; stay with her,"
or, alternatively, "HOLY CRAP, GET OUT NOW!"
One warning sign is code that is too needy; that is,
the code is so involved with the application that other
work cannot take place during the threaded work. For
example: Let's say you have a relatively simple game that
requires several thousand objects to be updated before
being rendered—a fairly typical task.
Now, there are ways
you can thread this to your advantage (a multi-threaded
renderer is one way; distributing the work between a
number of threads is another—I'll cover these in more
detail in later articles), but if you work on just threading
those object updates, your main thread will sit there
and wait for the updates to finish before sending the
commands to the renderer to draw the scene.
This is an
inefficient and a pretty useless threading application. It's
like going out with your friends, while your significant other
sits at home staring at the wall waiting for you to return.
If you can do other work on the main thread and do not want to or
can not split the work between the main thread and the object update
thread, structure the work you do on the main thread such that it
takes the same amount of time, or more, than the work that is run on
the update thread. Otherwise, your main thread will be sitting there
needlessly waiting for your update threads to finish. When it comes to
threading, idle hands are the devil's playground.
Things to look for in nicely-threadable code include functions that
do not necessarily have to be relied upon. For example, let's say
you want to run a real-time fast Fourier transform (FFT) on the music
playing in your game and render a visualization to the background.
FFTs are pretty expensive operations and can take a good chunk of
time depending on the complexity of the analysis of the data and
the type of data you want to extract from the results. From a singlethreaded
perspective, you would execute this process linearly, going
from FFT to analysis to rendering.
This type of code is perfect for threading because in most cases, you
do not have to update it every single frame. With modern games
rendering at 60 frames per second, the variation in the data that
the FFT provides from frame to frame will not differ much from the
last frame. Therefore you can throw the FFT and the data analysis
on a thread, run it, and then before rendering your background,
check to see if your FFT thread has finished executing. If so, use the
current data to render the background; if it is still running, use the last
calculated data to render the background.
You also need to look for work that can be split up between two or
more threads, so you can have one thread doing half the work, while
the main thread does the other half. Sure, there will be some overlap
where one thread is waiting for the other one to finish, but that
overlap will, in most cases, be very, very small, and if you design your
threading models to compensate for this all will be right in the world.