Sign in to follow this  

What do you thread?

This topic is 2570 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Since I recently had to delve back into threading for my game, I was curious as to what areas you use threading for?

I already know the most important parts I have to convert to threads in my game, this is just to see if maybe there might be other relevant areas I may have not thought of...

Share this post


Link to post
Share on other sites
Multiprocessing requires additional design and introduces complexity that is generally avoided in games.


Many times you can use threading with no significant work. Most I/O calls offer asynchronous versions. (I/O includes everything from graphics to audio to file system access.) This type of threading requires very little work on your end if you can do other things while waiting for it to complete. You will need to either query or handle a callback, but that is minimal effort for the huge benefit.

There are some fire-and-forget tasks you can do in C++, such as freeing up resources and assorted garbage collection. Other languages take care of this as a background task, and it is something you don't normally need to block on before it completes.

You can often break out time consuming tasks into their own threads, as was pointed out earlier. It requires some design work to make sure you don't corrupt your data, deadlock, or cause other problems. These can generally be placed in a lockless queue by the main game threads, processed and removed by a worker, and then added to a "done" queue when processing is complete. These tasks can include pathfinding, physics processing, and other tasks.

Those are the most common forms that you will encounter.


More complex forms require significantly more work.

Sometimes you can get great benefits from parallel processing algorithms; searching is the best example as it can give superscalar results. Master/Slave processing for trivially parallalizable functions and thread pools can work for you if you have a bunch of tasks with unknown duration and want to load balance on multi-core machines. Sometimes you can use it for parallel effects processing.

But they require more work to keep the processing thread safe to prevent data corruption, race conditions, and deadlocks, and other problems.

Finally, there are whole classes of parallel processing algorithms, as opposed to serial processing algorithms that are most often used. Generally they don't apply to interactive game processing today, instead focused on offline processing of large data sets. They are typically covered in the last years of undergraduate school and more in depth during graduate school. You generally won't see this type of thing in mainstream games, but expect it to change in the future.

Share this post


Link to post
Share on other sites
Animation, AI, view culling, displaylist preparations, particles, physics, file IO (and decompression), sound. There are tonnes of things in a game, that with proper design are quite parallel.

And I don't mean putting Animation in its own thread. Instead I mean breaking up animation by skeleton and droping all the animations into a thread pool to split the work up. Same with almost everything in my list. Games have tonnes of objects running around, and updating them all in parallel at different stages of the engine gives you some large gains.

Share this post


Link to post
Share on other sites
In short my plan is to do 'everything'.

Although to say I'm using threads would be slightly wrong as I'll be using tasks with the aforementioned Threading Building Blocks library so I constantly have N threads spinning over M tasks.

As I'll be targetting DX11 only threading becomes a 'no brainer' with graphics and I've spent some time of late figuring out how integrate IOCP with a task based system so that loading of data can be fired off and picked up X frames later when ready.

In short; forget about threads.
Thinking in tasks is the way forward [grin]

Share this post


Link to post
Share on other sites
Quote:
Original post by phantom
In short my plan is to do 'everything'.

[...]

In short; forget about threads.
Thinking in tasks is the way forward [grin]


+1.

Share this post


Link to post
Share on other sites
I have been considering using OpenMP for doing the thread work. As far as the difference between tasks and threads, I'm not getting your meaning. To me threads=tasks since I threaded the entire function, in this case my entire Ogg audio sub-system.

As for file I/O, in some cases I could do that but since it is a portal based system, I can't preload the next system until the player chooses to go there.

Share this post


Link to post
Share on other sites
It's all technically threads, but I think the point being made is to think in tasks, and program in tasks. A task scheduler is an interface to threading that has many benefits such as scalability, work stealing, thread handling etc. that allow you to throw your task into a system and have the threading efficiently handled for you.

There is more to it, but that's the basic idea of tasks. Hand threading is harder and not as efficient. But then again, some things are done better as threads - this is part of the complexity of designing a multithreaded game.

P.S. - can we please start a parallel game programming group? I feel like everyone that is doing this is hiding in corners :)

Share this post


Link to post
Share on other sites
Ahh understood. Thats pretty much the way I see it anyways. I trigger the thread at the start and forget about it. It handles the entire task of audio streaming. All I do is set a flag in the audio entry to say 'start playing'. Every 100 ms it scans the list of sounds, auto-starts any sounds waiting to be played. It also update any sound that currently playing if need be.

The 'hand threading' part? Is there any other type beyond using an external library which Im avoiding at all costs. I like to write my own code so I know exactly what it's doing and can fix any problems if they arise. Also, converting exisiting code usually is fairly trivial, it only took about an hour to completely convert the entire class.

Share this post


Link to post
Share on other sites
Quote:

origional post by LancerSolurus
As far as the difference between tasks and threads, I'm not getting your meaning.

It comes up a lot on the forums that people want to thread. And they think about threading in terms of full systems instead of small tasks.

The difference is between these esoteric examples:
thread1.Run( UpdateAllEntities() );
and
thread[0].Run( UpdateEntities( 1, 100 ) );
thread[1].Run( UpdateEntities( 100, 200 ) );
thread[2].Run( UpdateEntities( 200, 300 ) );
thread[3].Run( UpdateEntities( 300, 400 ) );

The theory is that there are few systems in a game, but many objects. You can split the update of individual objects across all the cores of the machine. But it requires a different design from non-threaded objects. You don't want things communicating between threads if you can avoid it, as synchronization will quickly become expensive. There are ways to deal with this, like message queues, and double-buffering your state data.

Share this post


Link to post
Share on other sites
You can create your own thread pool or task scheduling interface, but why not just use TBBs?

Tasks are generally better for finite stuff that can be split up into many parts, such as a for loop that works on lots of data independently. Threads are better for things like message handlers that are continuous through the life of the program and don't have many sub-levels. Such things won't be idling and therefore don't take advantage of a task design that would swap it in and out when needed.

Share this post


Link to post
Share on other sites
Quote:
Original post by LancerSolurus
To me threads=tasks since I threaded the entire function, in this case my entire Ogg audio sub-system.


A thread is a context for execution.
A task is a small unit of work which needs doing.

For example, updating an entity in a game could be considered a 'task'. Generating a render list would be a task.

The point is you start up N threads at the start of the up, and then as work needs doing it gets handed off to the thread pool and each thread gets handed a task to do.

So, instead of having one thread which handles audio all the time you would have an 'audio update task' which you called every M frames which handled any audio work which needed doing.

This means that instead of having threads idle and under using the CPU you get more work done.



Share this post


Link to post
Share on other sites
Quote:
Original post by Chris Reynolds
Threads are better for things like message handlers that are continuous through the life of the program and don't have many sub-levels.


In the context of a game a message handler is just another function which needs to be called.

The various tasks would posts messages into a queue, then at a set time in the update loop these would be processed.

There is no reason to keep a thread around just for this.

Share this post


Link to post
Share on other sites
Recently I'm threading my work-in-progress interactive ray tracing library (where threading in a good way is crucial - as it is probably the best way to use multiple cores ... e.g. on quad-core CPU 4 threads gives you ideally 4-times the speed, if you don't hit bottleneck elsewhere, F.e. in memory operations ... and also mutexes and synchronisations eats somethong), although I'm threading as much as possible if I can (and if algorithm is good for parallelisation of course!).

Share this post


Link to post
Share on other sites
Well the part I threaded is a 'task'. It only deals with handling audio playback, nothing else. All it does is make sure the audio plays continuously as need be and starts/stops sounds based on a single flag.

I've done threads before and making tons of threads usually ended up being bad. What I mean is it bogs down the CPU since the threads are all fighting for CPU cycles. The normal way promoted is to keep it to 1 thread per processor but I found that depending on the cpu cycles used that a single core can usually handle more than that.

For the cross-talk between threads, I won't be doing that at all. I prefer to keep it enclosed within itself. This avoids any race conditions, dead-locks and other problems that come with it.

Share this post


Link to post
Share on other sites
Yes, memory access patterns become VERY important when dealing with threads and tasks; cache lines suddenly become alot more important if you want good through put.

As for mutex/sync issues, its best to design to avoid the problem.

For example, in a game setting, when updating entites a good method is to have an 'external' and 'internal' state;
- when you update you can read anyones external state and update your internal
- then you have a sync step where you external state is updated to match your internal state

The update/sync method also allows you to seperate which data is shown to the rest of the 'world'.

The cache line thing is a bit more complicated, but it basically breaks down to you not wanting two threads accessing the same chunk of memory (delimited by cache line) for both read and write as it causes all manner of stalls which state is syncronised around the CPU, never mind the over head for a mutex around the operation.

Share this post


Link to post
Share on other sites
Quote:
Original post by LancerSolurus
Well the part I threaded is a 'task'. It only deals with handling audio playback, nothing else. All it does is make sure the audio plays continuously as need be and starts/stops sounds based on a single flag.


Something which would just as well in a system which used it as a task spread across a group of thread without spinning up a whole thread just to deal with it.

Quote:
Original post by LancerSolurus
I've done threads before and making tons of threads usually ended up being bad. What I mean is it bogs down the CPU since the threads are all fighting for CPU cycles. The normal way promoted is to keep it to 1 thread per processor but I found that depending on the cpu cycles used that a single core can usually handle more than that.


The whole point is that you don't start more threads than the CPU can handle, which avoids the overhead of context switches (for the most part, windows is an multi-tasking OS after all) and by spreading the work allows you to keep the CPU fully used.

For example, on an i7 920 you would start up a thread pool with 8 threads and use those to process groups of tasks as required.

Share this post


Link to post
Share on other sites
Quote:
Original post by Chris Reynolds
I'm not so sure... Why task something that doesn't ever idle and can't be broken into smaller pieces? I guess it depends on how you want your message handler to operate.


OK, so you've got an 8 core machine, you dedicate one thread to handling messages.
What does your thread do? Sit in a polling loop burning CPU time and 99% of the time doing nothing? What does it do when it gets a message? If it sends it directly to the recieving class they probably won't be in a state to deal with it then either, but in case they are then you'll need to invoke a mutex around the functionality for adding/removing messages from their queue which burns yet more CPU time.

So, by having a single thread running the whole time you;
- waste resources from idle processing (1/8th of nan i7 is ALOT of processing power)
- generate sync points when trying to push messages into reciving classes

By contrast, if you run a single task which distrubutes messages you;
- only run on demand thus free resources for other things
- can send messages at a point when you KNOW your reciving instances won't be touching their queue and as such can avoid lock stalls

There are very few, if any, pieces of functionality which need unbound, unsleeping, threads when processing in a game and doing so just add unpredictability to the system making debugging all the harder.

Share this post


Link to post
Share on other sites
For the built-in messenger I won't have any need to thread. I use a round-robin msg handler with a leader and follower pointer. At most there are usually less than 10 msgs in the table, I have 1024 slots available so I seriously doubt I would need more.

At the current time I only plan on threading the particle fx, culling system and AI handlers. The main renderer I don't plan on doing that since its pretty massive and doing that would mean an almost complete rewrite of the game.

Also, since most of the raw data is loaded at startup, there is no need to mess with any of the memory handlers. Only the models and textures have to be loaded upon switching scenes, most of the time that only takes a few seconds. I do plan on creating one to generate the jump tunnel though that renders while the main program loads the next scene (system in my case, it's a space game).

Share this post


Link to post
Share on other sites
I guess I concede my point, I'm just trying to get used to the idea that everything can and should be farmed out as a task or even a thread. I've talked to a lot of programmers, and there doesn't seem to be any consensus because so many things are specific to what the program is actually doing. I talked to an Intel engineer that told me that my idea of parallelizing everything is naive and that a lot of tasks are better off sequential. It's all so confusing!

It really all comes down to profiling your specific program I guess.

Share this post


Link to post
Share on other sites
Quote:
Original post by Chris Reynolds
I talked to an Intel engineer that told me that my idea of parallelizing everything is naive and that a lot of tasks are better off sequential. It's all so confusing!
However, those sequential tasks should be able to be run in parallel with each other! So even if you leave things as sequential, but make them isolated, you could still say you're "parallelizing everything", because you've made it so sequential tasks can be run in parallel.

Share this post


Link to post
Share on other sites

This topic is 2570 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this