Changing my thread design, and if so, the new design

Started by
2 comments, last by Thaumaturge 15 years, 10 months ago
I'm currently working on a game, a part of which involves the generating of "islands", and the textures that represent them, on the fly. In order to do this at a reasonable pace, I've implemented a thread-based system, in which the island's texture is generated in a separate thread, essentially guessing that it's improbable that the player will often reach generated islands before the thread has finished its work. At the moment I've implemented a pretty naive design (threading not being something that I've worked with very often, I don't think): each island, when created, starts up a new thread, and polls it each game update to see whether it has yet finished its work. Once the thread has finished, the island makes use of the produced data, and cleans up. The problem with this, of course, is that this can result in a fair few threads being produced, depending on the circumstances, and I've gathered that I should probably avoid spawning too many threads. Am I correct in this? I do have another design in mind. In this case, there is a single texture generator, which holds a single worker thread and a list of pointers to "tickets". When created, islands are given a pointer to the ticket that corresponds to their data, to which they store the data that is used to inform the texture generation process. Another pointer to this same ticket is pushed onto the back of the generator's list. The generator passes to the worker thread the data from the ticket, waits for the thread to report that it has concluded the generation of the relevant information (by polling in the game update function, not blocking the main thread), generates the texture map when it does, passes the id for that to the ticket (OpenGL doesn't like being called in worker threads, I believe), sets a flag in the ticket to alert the relevant island that its data is ready, and pops the ticket. If the island should be destroyed while its ticket is still in line, it sets a flag in the ticket to indicate this. If, on coming to a ticket, it discovers that it has been so invalidated, it simply discards it and moves on. Since there seems to me to be a possibility of a problem should an island be set to be destroyed while its data is being generated, given that islands will probably be responsible for the deletion of their texture bindings, I might have the generator set another flag in the ticket to let the island know that it shouldn't mark itself for deletion until the data has been generated and passed on. So, is this design a good idea? If I were to implement it, would you expect better, similar, or worse performance than my current method (given that I'm working on a single core machine)? Is there anything that I've missed? For those who might be wondering why I don't just implement the new design, it's because I'm not confident enough that it's a good idea, and if so, that it's sufficiently better than my current one to warrant the work involved. For those who are wondering why I'm considering changing at all, since things seem to be working at a reasonable speed, it's because I'm concerned by my gathered impression that spawning many threads is a bad thing, and, perhaps more salient, have been having a few problems that I think are connected to this method (including, for example, islands that are "physically" present, but invisible).

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

Advertisement
Quote:Original post by Thaumaturge
At the moment I've implemented a pretty naive design (threading not being something that I've worked with very often, I don't think): each island, when created, starts up a new thread, and polls it each game update to see whether it has yet finished its work. Once the thread has finished, the island makes use of the produced data, and cleans up.

Typically it's inefficient to poll for thread completion. You don't say which threading library/platform you're using, but you should look into using events to signal when a thread has finished it's work.
Quote:
So, is this design a good idea? If I were to implement it, would you expect better, similar, or worse performance than my current method (given that I'm working on a single core machine)?

On a single core, it should be slightly better assuming the same amount of processing is carried out in each case, as you are not carrying the overhead of thread startup and context switching. Clearly though, the code won't scale to multi-core machines.
Quote:
For those who might be wondering why I don't just implement the new design, it's because I'm not confident enough that it's a good idea, and if so, that it's sufficiently better than my current one to warrant the work involved.

It may suffer the opposite problem to your initial design: undersubscription rather than oversubscription. A reasonable scheme might be to implement a thread-pool scaled to the number of cores available. Although that depends if the potential problems are important enough to you that the added effort is worthwhile.
Somewhat too complex.

Let's assume that CPU is capable of completing this work in the first place. If it isn't, tough luck.

As such, you need one thread which will produce textures. This thread reads from a queue, generating texture as needed. When done, it notifies the caller of generated texture.

Application itself keeps running (it doesn't wait for anything. When it asks for a new area, a placeholder texture is used, and request to generator thread is issued. When generator is done, the placeholder texture is replaced with real one.

If the CPU is capable of generating textures in time, placeholders will never be seen. If there's a stall for one reason or another, placeholder textures will be sometimes visible for brief periods of time.

And that's about it. You can later spawn more generator threads (if, and only if, you have spare cores). Spawning multiple threads for computationally intensive tasks will not bring any benefits on an overtaxed CPU, it will likely make things worse.

Cancellation can be trivially performed by request holding a boolean flag. When this request is dequeued, and the flag is set, the generator will simply ignore it.

Quote:So, is this design a good idea? If I were to implement it, would you expect better, similar, or worse performance than my current method (given that I'm working on a single core machine)? Is there anything that I've missed?


It will be by definition worse than non-threaded model can be, simply because of more work and more shuffling of data. But it may make code simpler, and improve response time, since multi-threading will be performed by the OS, rather than you by partitioning the code.

If generation is computationally intensive, then you may have issues with generator thread claiming too many slices, causing poor responsiveness of your other threads. You can then mess with thread priorities, but that's tends to lead to thread starvation quickly.
Thank you both for your replies, and my apologies for the lateness of my own - I've been a little busy and preoccupied over the past two days. ^^;

Quote:Originally posted by Antheus
As such, you need one thread which will produce textures. This thread reads from a queue, generating texture as needed. When done, it notifies the caller of generated texture.

Application itself keeps running (it doesn't wait for anything. When it asks for a new area, a placeholder texture is used, and request to generator thread is issued. When generator is done, the placeholder texture is replaced with real one.

...

Cancellation can be trivially performed by request holding a boolean flag. When this request is dequeued, and the flag is set, the generator will simply ignore it.


That sounds more or less like what I described (although I am tired tonight, so I might well be missing something) - what complexity am I missing in my model, or simplicity in yours?

I do have data that I want to pass from the island to the thread (although I suppose that I can move that calculation to the thread, although that means passing the same data back, since the data in question is also used by the island itself), so I think that the "ticket" system - which seems to be little more than a slightly more complex version of the "request" system that you describe - does call for some internal data aside from a cancellation flag, at least. I could, I suppose, provide a pointer to the island itself, come to think of it...

(These "islands" have their "physical" shape defined by a 2D grid, which is also used to inform the texture generation algorithm.)

Quote:Originally posted by K-
Typically it's inefficient to poll for thread completion. You don't say which threading library/platform you're using, but you should look into using events to signal when a thread has finished it's work.


Argh, that's a good point. >_<

I originally used an event-based system, but had some problem with it, I think (which might have been related to FOX, the toolkit that I'm using). However, I recently downloaded a new version, so I think that I'll give it another shot.

Quote:Originally posted by K-
It may suffer the opposite problem to your initial design: undersubscription rather than oversubscription. A reasonable scheme might be to implement a thread-pool scaled to the number of cores available. Although that depends if the potential problems are important enough to you that the added effort is worthwhile.


Funnily enough, I believe that the development branch of FOX has a thread pool implementation. Unfortunately, having tried it out, it looks as though that development API is sufficiently different to the current stable interface that converting might be a bit of a pain. At the moment, I doubt that the issue is serious enough to warrant either the API change or my own thread pool implementation.

Quote:Originally posted by Antheus
Let's assume that CPU is capable of completing this work in the first place. If it isn't, tough luck.


Quote:Originally posted by Antheus
It will be by definition worse than non-threaded model can be, simply because of more work and more shuffling of data. But it may make code simpler, and improve response time, since multi-threading will be performed by the OS, rather than you by partitioning the code.


Heh, of course. The point of threading in this case is less to speed things up than to make the time taken to generate the textures less visible.

In this case, since the game is top-down, the "islands" are generally not initially visible, giving me a little time in which to generate their textures. In this case I think that "invisible" (that is, having no texture) works better than a temporary texture.

"Performance" was, in retrospect, a poor choice of words, at least without qualification. What I'm after, of course, is smoothness, without losing too much performance.

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

This topic is closed to new replies.

Advertisement