[olofsonprojects] Re: Portable thread local storage?

  • From: David Olofson <david@xxxxxxxxxxx>
  • To: olofsonprojects@xxxxxxxxxxxxx
  • Date: Sat, 28 Nov 2009 21:56:04 +0100

On Saturday 28 November 2009, at 14.03.22, Damyan Ivanov <dmn@xxxxxxxxxx> 
wrote:
> -=| David Olofson, Sat, Nov 28, 2009 at 02:37:08AM +0100 |=-
> 
> > I'm working on an audio processing framework [*] with an API in the
> > style of OpenGL and OpenAL. For various reasons (probably similar to
> > those that led the OpenGL and OpenAL designers to the same
> > decision), I'm using integer names, as opposed to pointers, to
> > reference objects.
> 
> I wonder what these reasons are.

Well, my reason is that there are some cases where you have to tell one object 
about another, such as when connecting ports. I'd rather avoid pointers on 
that level of the API, as that would require an extra port/value type. Also, 
64 bit pointers are pretty large, especially when you need to fit a bunch of 
them into a generic command struct/union.

Waste a few bytes here, an a few there in frequently used structs, and you'll 
soon have a big, slow behemoth of a library. ;-)

Anyway, now that I think about it, I suppose the "another port/value type" 
argument is pretty much moot. I need support for multiple port types anyway! 
(Including "custom" ones, for add-on packages with units that pass around FFT 
spectra and other stuff. I use URIs that are mapped to integers at load time.) 
Now, and as soon as you have more than one type, you need that "connect only 
compatible ports" logic. The hairy part is when you have "almost" compatible 
ports (such as fixed point/floating point), and want converters and stuff - 
but  pointers cannot be converted from or into anything, so they'd have no 
bearing on that stuff.


> Using pointers leads straight to 3. below, without any overhead I can see.
> You have to dereference object names anyway - look them up in an array (i.e.
> dereferencing another pointer).

Yes... A direct pointer is faster than a look-up; fewer operations and no 
table to fit in the cache.


> And I thing passing around a pointer would have same properties
> performance-wise as an integer.

Well, my pointers are twice as large as my integers. ;-) Still, I don't think 
that makes any significant difference on this level. After all, the normal 
application would only use these API calls a few hundred times per second, in 
extreme cases. (A few calls per triggered sound effect.)

For comparison, Kobo Deluxe running over glSDL makes a few thousand OpenGL 
calls per *frame* in the default configuration... :-)


> >     1. Add an a2_MakeCurrent() call, to select the target context
> >        for subsequent context related API calls. Integer names are
> >        context local, and there are no context names or pointers
> >        being passed to any normal API calls.
> 
> As you point below, this is really ugly for multi-threading.
> Thread-local storage brings some performance penalties, I've heard.

The overhead would depend on the implementation, I suppose... VMT, MMU or OS 
calls, basically.

Read the Nils' answer here:
        
http://stackoverflow.com/questions/506093/why-is-thread-local-storage-so-slow

The MMU way could actually be zero overhead (beyond the usual OS context 
switching overhead, that is), whereas the VMT approach is about as fast as any 
of our pointer look-up designs. No problems there.

Having every command call make a bunch of extra syscalls - not very nice at 
all. OTOH, like I said, we have maybe a few hundred calls per second in 
extreme cases, so it's not the end of the world...


Anyway, discussing this matter on another list (GameProgrammer), I realized 
that I've overlooked one thing: Applications that want to talk to the same 
context (you normally have only one realtime context) from different threads.

For example, this would happen in a game that runs "decoupled" game logic in a 
separate thread; you'd get calls from the logic thread most of the time, and 
occasional calls from the GUI.

Option 1 could handle this transparently, by automatically create a separate 
connection for each thread that calls a2_MakeCurrent() on the same context. 
(The "connection" here is a single-writer/single-reader, lock-free FIFO for 
commands.)

Options 2 and 3 can't really handle this, without also relying on thread local 
storage - and then one may as well go with option 1.

However, one could avoid thread local storage, name pool thread safety issues 
and all that by a new option,

        4. Introduce a new abstract object, A2_Interface, and add a
           call
                A2_Interface *a2_Interface(A2_Int context);
           that creates an interface that you use for issuing
           commands to the context. Instead of passing a context
           pointer to commands, you pass this interface pointer.
           Each thread wanting to communicate with a context will
           need to create it's own interface to it.

That is, same deal as option 3; you need to pass a pointer to each command 
call - but now you can talk to the same context from multiple threads.

Of course, one can use the same pointer type for contexts and interfaces, and 
have the context constructor actually return an interface. You could then use 
any interface to create another interface. If you do issue all commands from 
the same thread, you can just forget about interfaces and think about this as 
option 3, plain and simple.


> >     2. Encode the parent context of objects in the integer names.
> >        (This is sort of like passing a context pointer around, but
> >        you hide this by extracting the context out of other object
> >        references that you'll need to use with any API "command"
> >        call anyway.)
> 
> If pointers are used, each object can naturaly hold a reference to its
> context.

Yes - but that adds 4 or 8 bytes to the size of every object, and the usual 
extra indirection is there as well. Probably no big deal, though.

(In heavy applications, we're talking up to a few dozens of objects per 
"voice", and people might be using a few hundred voices in extreme cases - but 
then we're probably in a studio, and most likely not running on an iPhone!)

Oh, and this would work even if objects are named, of course. That's one way 
of not having to mess as much with the name bits...


> >     3. The standard "OOP" style solution: Explicitly pass a context
> >        pointer to any API function that deals with a context.
> 
> Maybe you can keep the parents in a global array indexed by object
> name? It would imply that objects are named from 0 on though and
> perhaps will need keeping a bitmap of used/free indices.

There will have to be something like that for the name lookup anyway; either a 
global registry of sorts, or one per context.

Currently, I have a "tree" with three fixed levels of tables, to avoid ever 
reallocating the root table (that would require locking), and to avoid 
reallocating huge memory blocks. (Reallocation typically means allocating a 
new block and copying the data.)

An alternative to bitmaps would be to arrange all unused slots in a linked 
list, so you can just grab one with an O(1) operation. When you free a name, 
you add it to the list.

In order to "scale back" as objects are deleted, one could add a "used" count 
to each block of pointers, and free the block if that count reaches zero. The 
problem is finding the right block, if using the linked list approach, but 
that can be dealt with by aligning the blocks suitably in memory, so you 
calculate the block address from the address of your object pointer.


> Sorry if my ideas are too naive :) I hope that they would at least
> point you to some new approaches.

Any input and inspiration is valuable here! :-) There are so many ways to do 
this, and I probably haven't even considered half of them yet...


-- 
//David Olofson - Developer, Artist, Open Source Advocate

.--- Games, examples, libraries, scripting, sound, music, graphics ---.
|  http://olofson.net   http://kobodeluxe.com   http://audiality.org  |
|  http://eel.olofson.net  http://zeespace.net   http://reologica.se  |
'---------------------------------------------------------------------'
---------------------------------------------------------------------------
The Olofson Projects mailing list.
Home:              http://olofson.net
Archive:           //www.freelists.org/archives/olofsonprojects
Unsubscribe: email olofsonprojects-request@xxxxxxxxxxxxx subj:"unsubscribe"
---------------------------------------------------------------------------

Other related posts: