[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Linux sound (and threads)



Jeff Read wrote:

> > Well, if it works correctly now, it should be fine, but I think that in
> > the long run, out of process could be a bad idea, particularly in the
> > area of games like Quake2, where gamers complain that a single machine
> > gets 34 fps in Windows and only 33.2 in Linux... ;-)
> 
> Well, the sound code is such that it can be embedded in-process or
> migrated out. Should the need arise I can cut-and-paste the code out of
> xhsound and into a module which is called on every frame refresh.

I thought about it a bit, and if you sleep "enough" (exactly what amount
is "enough" is left as an exercise to the reader) in your helper thread,
that might have much less of an impact on the main thread efficiency, as
this would yield a behavior comparable to a daemon process, not waking
up often enough to be a nuisance. But I'd avoid it as much as possible,
just to be sure (and to avoid trying to divine how much sleep would be
"enough"!).

> > With only two threads in such a manner, you could only use a mutex for
> > an queue with move events coming from the input thread, so that's not
> > too bad as far as "complicated" goes. But is the render thread looking
> > often at the queue? If so, it has the lock often, and the input thread
> > has to wait. Or if the input thread got the lock, the render thread goes
> > dead in the mean time... This is taking it a bit simply though. I guess
> > you could "try" to get the lock for a few milliseconds, then, seeing
> > that you can't have it now, go and render some stuff. But now it is
> > getting more complicated...
> 
> Well, the render thread simply does, "move, render, sleep for 20ms,
> repeat". Win32 has a critical section mechanism that's optimized for two
> or more threads in the same process, and faster than mutexes (mutices?)
> in this case. The render thread would take the critical section, move
> and render the sprites, and then relinquish it when it was done. The
> event thread would only try to take the critical section when it wanted
> to change some property of a sprite while the render thread was
> rendering. (I say "take" and "relinquish" because like mutices,
> semaphores, and the famous Perl "patch pumpkin" Win32 critical sections
> are objects which are passed around, the possession of which guarantees
> you exclusive use of the resources it governs.)
> 
> The Win32 function EnterCriticalSection() would attempt to take the
> critical section, and busy-wait until it was available. The render
> thread got a higher priority than the event thread, so it would hang
> onto the critical section a bit more often. When it slept, the event
> thread would jump in and do its thing. It was a pretty simple
> multithreading example, but a tad on the slow side. Eventually I plan on
> migrating Sprite32 back onto the Win32 side of things, making it a bit
> more portable so games could be compiled for Linux and Win32 without
> code changes. If I had to do it all over again, everything would go back
> into that single thread.
> 
> *sigh* I miss Win32. IMHO 'Doze 95 and NT are bad implementations of
> some pretty good ideas. But hey, at least Wine rules :)

Yes, this is the "simpler, but a lot slower" way. When you call
EnterCriticalSection(), you could wait after the other thread for a bit,
while it is in the critical section... I agree 100% with John Carmack:
one CPU, one thread. And a lot of black magic can be helpful with
threads. :-)

> You should have no problems understanding how to program for Sprite32,
> then :) All of the Sprite objects are stored in a linked list. The Run()
> method of SpriteAppWin then iterates down the list, first calling the
> virtual MoveSprite() method of every sprite, and then RenderSprite()
> which is virtual but is nine times out of ten the same for each sprite.
> It's quite fast, even when you have a lot of things on the screen at
> once. MoveSprite can become pretty hairy when there's a lot of state
> involved that you wish to keep track of. But you can create lots of cool
> effects by coding each sprite's methods as if they were independent
> little programs instead of wondering, "Now where am I in the loop and
> what do I have to do here". (I know that Gnudius has a lot of cheap,
> two-dollar effects such as the stippled fade-in-and-out background
> effect, but still...)

I remember doing a cooperative threading system for Borland Pascal in my
DOS days (hey, this was *Borland* Pascal, not the "diet" Turbo Pascal!
16-bit protected mode, yeah!), and it could context switch in the other
of 25k-30k times per second! Those were were small threads, calling the
yield() function a lot, but I can imagine having a few hundred sprites,
maybe even in the thousand, and having them all "run" in each frame,
even with a good framerate...

> > Hmm... QuickThreads, eh? Sure that I'll take a look at this! But I
> > wouldn't wager this library being under a looser license than the
> > LGPL...
> 
> The copyright is actually very loose and is BSDish, like the Wine
> license. Guile itself is GPLed; QuickThreads is a separate package
> altogether that's just bundled in because it was used to implement
> threading in Guile. From the traffic that's going on in the Guile ML,
> they really want to rewrite Guile threading to use kernel-level threads,
> if available. So get QuickThreads free in specially marked boxes of
> Guile, while supplies last. :)

BSDish licensing, now you're getting my attention! Thanks also for
pointing out the "while supplies last"! :-)

One thing that seems interesting as a source of ideas is the Netscape
NSPR that has been liberated with Mozilla. It has user threads
(non-preemptives), and it has a kind of virtual CPU concept, and can mix
the user threads with kernel threads. So the threads that the program
creates are basically non-preemptive threads, but NSPR creates a "CPU"
kernel thread that takes care of a few of the preemptive threads. So if
you have 8 user threads and four (real) CPUs, you can tell NSPR how
manyCPUs you want to use (say three, leaving one of the OS), and it
will create a total of three kernel threads that will each take a number
of the user threads.

So you get the best of both worlds. Or at least you're supposed to,
because there is a downside: what would normally be non-preemptive user
threads now have to use "real" locking, mutexes and critical sections,
because there could be another user thread running at the same time on
another virtual CPU (which are preempted)...

I'd say that for most games, user threads (like the NPS package I told
you about) are the best deal you could get, with *no* critical sections
or locks or mutexes needed, because you are *always* in a critical
section and you explicitly hand over the control to the scheduler. The
downside of being unable to exploit multiple CPU machine, I don't think
this is most of a problem, as it is something of a black art anyway and
will have to be done in a very specific and controlled way. Nothing
stops you from using pthreads at the same time as NPS (in fact, I think
there is a hook for this, something about forking "properly" the
threading system).

-- 
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
"First they ignore you. Then they laugh at you.
Then they fight you. Then you win." -- Gandhi