To Pd dev -
For some time the good folks who brought us pdlib have been asking how one could make it possible to run several instances of Pd in a single address space.
The question I'd like to rais is this: would it suffice to make Pd instances be per-thread? This could be done by going through all the source and modifyin global and static variables with the nonstandard __thread leyword (gcc) or some declspec horror in Miscoroft C. Alternatively, one could use the C++11 standard thread_local keyword, although I believe that's not widely implemented yet.
To do this I'd replace all globals like static t_symbol *symhash[HASHSIZE]; with PDSTATIC t_symbol *symhash[HASHSIZE];
and define PDSTATIC to static (also PDGLOBAL to the empty string and PDEXTERN to extern). Then anyone wanting to come along and recompile pd to work per-thread only has to redefine the three appropriately to the compiler.
Here are the gotchas I can foresee:
1. external objects making explicit references to global storage (such as canvas_dspstate and cos-table in m_pd.h and much stuff in the more 'private' header files) would have to be recompiled to run per-thread. They'd still work fine with vanilla Pd.
2. existing externs that create threads would break at the source level if they use any Pd-supplied functions at all (outlet_bang(), clock_set(), gensym(), etc) in 'other" threads. Again they'd still work in Pd vanilla, just not with versions of Pd recompiled to run per-thread.
3. lots of lines of code would be touched and this might make a number of existing patches fail to apply cleanly.
4. supposing you use this to make a VST plug-in: what would happen if some stupid host app called all its VST plug-ins from the same thread? (I think it's normal for hosts always to make a new thread for every VST plug-in instance but I don't know if this is universal).
5. If you wanted to run two instances of Pd in the same thread this wouldn't help. You'd have to spawn new threads and pass control to them to get into the alternate Pds.
Comments anyone?
thanks Miller
I don't really have a sense of other possible approaches and their advantages/disadvantages. Couldn't this issue also be solved in the logic of the code? A lot of the current global variables could easily be visible in each thread, and still be fully functional. Things like sys_libdir and the like. So it doesn't seem like all global vars are the issue.
.hc
On Jan 14, 2012, at 4:04 PM, Miller Puckette wrote:
To Pd dev -
For some time the good folks who brought us pdlib have been asking how one could make it possible to run several instances of Pd in a single address space.
The question I'd like to rais is this: would it suffice to make Pd instances be per-thread? This could be done by going through all the source and modifyin global and static variables with the nonstandard __thread leyword (gcc) or some declspec horror in Miscoroft C. Alternatively, one could use the C++11 standard thread_local keyword, although I believe that's not widely implemented yet.
To do this I'd replace all globals like static t_symbol *symhash[HASHSIZE]; with PDSTATIC t_symbol *symhash[HASHSIZE];
and define PDSTATIC to static (also PDGLOBAL to the empty string and PDEXTERN to extern). Then anyone wanting to come along and recompile pd to work per-thread only has to redefine the three appropriately to the compiler.
Here are the gotchas I can foresee:
- external objects making explicit references to global storage
(such as canvas_dspstate and cos-table in m_pd.h and much stuff in the more 'private' header files) would have to be recompiled to run per-thread. They'd still work fine with vanilla Pd.
- existing externs that create threads would break at the source level if
they use any Pd-supplied functions at all (outlet_bang(), clock_set(), gensym(), etc) in 'other" threads. Again they'd still work in Pd vanilla, just not with versions of Pd recompiled to run per-thread.
- lots of lines of code would be touched and this might make a number of
existing patches fail to apply cleanly.
- supposing you use this to make a VST plug-in: what would happen if some
stupid host app called all its VST plug-ins from the same thread? (I think it's normal for hosts always to make a new thread for every VST plug-in instance but I don't know if this is universal).
- If you wanted to run two instances of Pd in the same thread this wouldn't
help. You'd have to spawn new threads and pass control to them to get into the alternate Pds.
Comments anyone?
thanks Miller
Pd-dev mailing list Pd-dev@iem.at http://lists.puredata.info/listinfo/pd-dev
----------------------------------------------------------------------------
I spent 33 years and four months in active military service and during that period I spent most of my time as a high class muscle man for Big Business, for Wall Street and the bankers. - General Smedley Butler
Hi Miller, Thanks for your message!
I'm afraid thread-local instances would be problematic from the point of view of libpd:
* The most common structure of a libpd-based application involves two threads, a GUI thread and an audio thread, where the GUI controls the Pd engine by invoking libpd functions from its thread. This approach would break if Pd instances were thread local.
* In many cases, the audio thread is beyond the control of the programmer. For instance, if you want one Pd instance per JACK client, or one Pd instance per audio unit in iOS, then you just register a callback, and you have no real idea which thread will ultimately invoke your callback.
* If you use libpd via its Python bindings, then your threading options are limited due to the Global Interpreter Lock.
* libpd may also end up doing batch-processing, either by design or as a client running under JACK's freewheel mode, which has implications for threading.
I have a few more concerns, but these are the most important ones. The upshot is that libpd may run in such a wide range of settings that it's hard to make assumptions about what kind of approach to threading is appropriate or even available. Cheers, Peter
On Sat, Jan 14, 2012 at 4:04 PM, Miller Puckette msp@ucsd.edu wrote:
To Pd dev -
For some time the good folks who brought us pdlib have been asking how one could make it possible to run several instances of Pd in a single address space.
The question I'd like to rais is this: would it suffice to make Pd instances be per-thread? This could be done by going through all the source and modifyin global and static variables with the nonstandard __thread leyword (gcc) or some declspec horror in Miscoroft C. Alternatively, one could use the C++11 standard thread_local keyword, although I believe that's not widely implemented yet.
To do this I'd replace all globals like static t_symbol *symhash[HASHSIZE]; with PDSTATIC t_symbol *symhash[HASHSIZE];
and define PDSTATIC to static (also PDGLOBAL to the empty string and PDEXTERN to extern). Then anyone wanting to come along and recompile pd to work per-thread only has to redefine the three appropriately to the compiler.
Here are the gotchas I can foresee:
- external objects making explicit references to global storage
(such as canvas_dspstate and cos-table in m_pd.h and much stuff in the more 'private' header files) would have to be recompiled to run per-thread. They'd still work fine with vanilla Pd.
- existing externs that create threads would break at the source level if
they use any Pd-supplied functions at all (outlet_bang(), clock_set(), gensym(), etc) in 'other" threads. Again they'd still work in Pd vanilla, just not with versions of Pd recompiled to run per-thread.
- lots of lines of code would be touched and this might make a number of
existing patches fail to apply cleanly.
- supposing you use this to make a VST plug-in: what would happen if some
stupid host app called all its VST plug-ins from the same thread? (I think it's normal for hosts always to make a new thread for every VST plug-in instance but I don't know if this is universal).
- If you wanted to run two instances of Pd in the same thread this
wouldn't help. You'd have to spawn new threads and pass control to them to get into the alternate Pds.
Comments anyone?
thanks Miller
Pd-dev mailing list Pd-dev@iem.at http://lists.puredata.info/listinfo/pd-dev
Yikes... sounds like back to the drawing board.
The odd thing is, do make thread-local storage, the good C compiler folks (and linker folks, etc) had to do all the work you'd need to make a switch-all-my-static-storage-when-I-ask-you-to feature which would open up all sorts of other ways to do things. But I don't see any way to adapt it to our needs, given the concerns you raised below.
cheers Miller
On Sat, Jan 14, 2012 at 10:16:17PM -0500, Peter Brinkmann wrote:
Hi Miller, Thanks for your message!
I'm afraid thread-local instances would be problematic from the point of view of libpd:
- The most common structure of a libpd-based application involves two
threads, a GUI thread and an audio thread, where the GUI controls the Pd engine by invoking libpd functions from its thread. This approach would break if Pd instances were thread local.
- In many cases, the audio thread is beyond the control of the programmer.
For instance, if you want one Pd instance per JACK client, or one Pd instance per audio unit in iOS, then you just register a callback, and you have no real idea which thread will ultimately invoke your callback.
- If you use libpd via its Python bindings, then your threading options are
limited due to the Global Interpreter Lock.
- libpd may also end up doing batch-processing, either by design or as a
client running under JACK's freewheel mode, which has implications for threading.
I have a few more concerns, but these are the most important ones. The upshot is that libpd may run in such a wide range of settings that it's hard to make assumptions about what kind of approach to threading is appropriate or even available. Cheers, Peter
On Sat, Jan 14, 2012 at 4:04 PM, Miller Puckette msp@ucsd.edu wrote:
To Pd dev -
For some time the good folks who brought us pdlib have been asking how one could make it possible to run several instances of Pd in a single address space.
The question I'd like to rais is this: would it suffice to make Pd instances be per-thread? This could be done by going through all the source and modifyin global and static variables with the nonstandard __thread leyword (gcc) or some declspec horror in Miscoroft C. Alternatively, one could use the C++11 standard thread_local keyword, although I believe that's not widely implemented yet.
To do this I'd replace all globals like static t_symbol *symhash[HASHSIZE]; with PDSTATIC t_symbol *symhash[HASHSIZE];
and define PDSTATIC to static (also PDGLOBAL to the empty string and PDEXTERN to extern). Then anyone wanting to come along and recompile pd to work per-thread only has to redefine the three appropriately to the compiler.
Here are the gotchas I can foresee:
- external objects making explicit references to global storage
(such as canvas_dspstate and cos-table in m_pd.h and much stuff in the more 'private' header files) would have to be recompiled to run per-thread. They'd still work fine with vanilla Pd.
- existing externs that create threads would break at the source level if
they use any Pd-supplied functions at all (outlet_bang(), clock_set(), gensym(), etc) in 'other" threads. Again they'd still work in Pd vanilla, just not with versions of Pd recompiled to run per-thread.
- lots of lines of code would be touched and this might make a number of
existing patches fail to apply cleanly.
- supposing you use this to make a VST plug-in: what would happen if some
stupid host app called all its VST plug-ins from the same thread? (I think it's normal for hosts always to make a new thread for every VST plug-in instance but I don't know if this is universal).
- If you wanted to run two instances of Pd in the same thread this
wouldn't help. You'd have to spawn new threads and pass control to them to get into the alternate Pds.
Comments anyone?
thanks Miller
Pd-dev mailing list Pd-dev@iem.at http://lists.puredata.info/listinfo/pd-dev
Le 2012-01-14 à 22:16:00, Peter Brinkmann a écrit :
- In many cases, the audio thread is beyond the control of the
programmer. For instance, if you want one Pd instance per JACK client, or one Pd instance per audio unit in iOS, then you just register a callback, and you have no real idea which thread will ultimately invoke your callback.
Even when you do have control over threading, as on Android, you have several reasons to want to split your app in several parts and have them communicate and that means either being able to call pd functions from any thread or inventing elaborate workarounds.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
i think there are 2 use cases for multi "threading".
#1 access a single instance of (lib)pd from multiple threads #2 allow multiple instances of (lib)pd to co-exist in global memory.
right now, only #1 is possible at all and it takes some effort on the "thread host" (the external) to not fuck Pd's heap. i think Pd should be more helpful in this respect: a way to make Pd thread-safe is to eliminate global variables and if they can't (or shan't) be eliminate them properly protect them against parallel access.
On 2012-01-14 22:04, Miller Puckette wrote:
To do this I'd replace all globals like
what is wrong with eliminating all directly accessible globals from the API (like "pd_objectmaker") and provide accessor functions to get (thread safe) access to them?
e.g. i would prefer if the symhash stayed global and instead access to it (via gensym()) was thread safe.
i guess the main reason for not doing this is performance(?)
otoh, the subject indicates that you are more talking about case #2. in this case what is needed is to replace the global state by a per-instance state. i would rather _not_ tie the concept of "instance" to the concept of "thread" (even though in practice they might often be interchangeable).
afaik, the usual way to accomplish a concept of multiple instances is to aggragate all currently global variables into a "context" and extend the API so that each function has an additional parameter for this context. this will obviously break API compatibility... a possible workaround would be to add a new function "t_pdcontext*make_current(t_pdcontext*)" that would change the running instance and all API calls would henceforth work on the current context. this would still need a global variable (the current context). the "make_current" call could update the legacy global variables to contain the "current" values. (this would only work as long as no code caches the values of these variables).
of course this doesn't really solve the problem of concurrent access to multiple instances of (lib)pd. in this case an extended API that has an explicit reference to the currently used API should help.
anyhow, i would strongly suggest not to use some compiler magic that might or might not be supported for older (and newer) compilers available on the market for (lib)pd itself. e.g. c++11 (c++? ever tried to compile Pd with a c++ compiler?) is supported only by fairly new compilers (and afaik, that support is only partially, even on g++)
fgasdr IOhannes
On Mon, Jan 16, 2012 at 3:43 AM, IOhannes m zmoelnig zmoelnig@iem.atwrote:
i think there are 2 use cases for multi "threading".
#1 access a single instance of (lib)pd from multiple threads #2 allow multiple instances of (lib)pd to co-exist in global memory.
That's right, and there are real use cases for both of them. An example of the former is the case I mentioned earlier, a libpd-based app with a GUI thread and an audio thread. An example of the latter are audio nodes in the jReality scene graph; it would be great to have one Pd instance per audio node.
right now, only #1 is possible at all and it takes some effort on the "thread host" (the external) to not fuck Pd's heap. i think Pd should be more helpful in this respect: a way to make Pd thread-safe is to eliminate global variables and if they can't (or shan't) be eliminate them properly protect them against parallel access.
As far as libpd is concerned, I would prefer not to have any synchronization inside Pd itself --- libpd can be used in a wide variety of settings, with lots of different approaches to concurrency, and so it's impossible to make any assumptions about threading at this level.
Of course, as you point out, Pd itself requires some synchronization in its interaction with externals, so there's a bit of a conflict there. My favorite solution would be to refactor Pd so that it has an audio library much like libpd at its core. Then Pd would be able to do all the synchronization it needs without affecting other applications that use the same library.
On 2012-01-14 22:04, Miller Puckette wrote:
To do this I'd replace all globals like
what is wrong with eliminating all directly accessible globals from the API (like "pd_objectmaker") and provide accessor functions to get (thread safe) access to them?
e.g. i would prefer if the symhash stayed global and instead access to it (via gensym()) was thread safe.
I'd rather not have any shared global state at all because that would significantly reduce the possible performance gains from concurrency (Amdahl's Law).
i would rather _not_ tie the concept of "instance" to the concept of "thread" (even though in practice they might often be interchangeable).
Agreed.
afaik, the usual way to accomplish a concept of multiple instances is to aggragate all currently global variables into a "context" and extend the API so that each function has an additional parameter for this context. this will obviously break API compatibility...
The solution I have in mind would add an extended API that has a context parameter everywhere. In order to maintain compatibility with current code, there would be a global legacy context, and the functions in the current API would simply invoke their new counterparts with this global context.
anyhow, i would strongly suggest not to use some compiler magic that might or might not be supported for older (and newer) compilers available on the market for (lib)pd itself.
Agreed. Also, there has been talk of using libpd in embedded systems, which may come with exotic compilers of their own. Cheers, Peter
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 2012-01-19 07:09, Peter Brinkmann wrote:
On 2012-01-14 22:04, Miller Puckette wrote:
To do this I'd replace all globals like
what is wrong with eliminating all directly accessible globals from the API (like "pd_objectmaker") and provide accessor functions to get (thread safe) access to them?
e.g. i would prefer if the symhash stayed global and instead access to it (via gensym()) was thread safe.
I'd rather not have any shared global state at all because that would significantly reduce the possible performance gains from concurrency (Amdahl's Law).
it seems like i was myself mixing instances and threading. indeed what i would prefer was, if i could use gensym() from another thread in a safe way. this has nothing to do with a global hashtable (and i don't see a reason why multiple instances should share a global hashtable)
right now, only #1 is possible at all and it takes some effort on the "thread host" (the external) to not fuck Pd's heap. i think Pd should be more helpful in this respect: a way to make Pd thread-safe is to eliminate global variables and if they can't (or shan't) be eliminate them properly protect them against parallel access.
As far as libpd is concerned, I would prefer not to have any synchronization inside Pd itself --- libpd can be used in a wide variety of settings, with lots of different approaches to concurrency, and so it's impossible to make any assumptions about threading at this level.
what are the actual drawbacks if e.g. clock_delay() could be used from any thread (in an external)? which assumptions are made that might not hold true in all your use cases?
my main reasoning is, that thread synchronisation is a re-curring problem that imho should not be re-implemented whenever it is needed. also a earlier attempts to fix this, using the great BIG kernel lock (aka sys_lock()), proved (at least for me) to be inadequate, as they slow down the entire processing significantly.
however, maybe i'm asking too much and what i really want is to have a standardized possibility to send messages to a Pd-instances without having to worry about threading (i use all the clock-stuff i keep mentioning mainly for doing exactly this: implementing a thread-safe message queue to send data from a worker thread back to Pd)
Of course, as you point out, Pd itself requires some synchronization in its interaction with externals, so there's a bit of a conflict there. My favorite solution would be to refactor Pd so that it has an audio library much like libpd at its core. Then Pd would be able to do all the synchronization it needs without affecting other applications that use the same library.
i have to admit i'm a bit unsure where you would draw the separation. Pd uses common infrastructures for a lot of things on different system levels: e.g. the message system is used to communicate between objects, to let the gui talk to the pd-core, to open a patch (instantiate it's objects),... while i think this is one of the strengths of Pd, i also think that this is probably the biggest problem when attempting a refactor as you describe it (but again: i might totally miss the point here)
The solution I have in mind would add an extended API that has a context parameter everywhere. In order to maintain compatibility with current code, there would be a global legacy context, and the functions in the current API would simply invoke their new counterparts with this global context.
yes that's what i wanted to say (and please forget about the make_global() idea; having a single global legacy context is of course much easier
fgmasdr IOhannes
Le 2012-01-19 à 10:11:00, IOhannes m zmoelnig a écrit :
it seems like i was myself mixing instances and threading. indeed what i would prefer was, if i could use gensym() from another thread in a safe way. this has nothing to do with a global hashtable (and i don't see a reason why multiple instances should share a global hashtable)
Now that I think of it, they can't. I momentarily forgot something obvious.
This is because Pd's symtable is like an old LISP's symtable : the symtable isn't just a mapper of const char[] to t_symbol *, it's also giving you access to s_thing, which is the mechanism for naming [table], [value], [delwrite~], [receive], [send~], [catch~], and some more.
In some other systems (Smalltalk, Ruby, etc), the symtable is just a set of permanent strings, and there are various vartables to hold the mappings of t_symbol * to t_pd * (I'm translating this in pd terminology). Nearly all languages have such a system because it allows them to avoid having to turn $0-myname into 1234-myname just to be able to read a puny local variable.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Le 2012-01-19 à 01:09:00, Peter Brinkmann a écrit :
As far as libpd is concerned, I would prefer not to have any synchronization inside Pd itself --- libpd can be used in a wide variety of settings, with lots of different approaches to concurrency, and so it's impossible to make any assumptions about threading at this level.
If you avoid making any assumptions about threading inside Pd, then you can't share gensym and its static table with any other instance, because you can't ask all callers of gensym to do the locking on gensym. You can't ask each piece of software to do its own locking on gensym, because then they won't see each other's locks. Therefore, to share gensym, the locking must be inside gensym.
Sharing gensym is useful so that one can prepare lists and messages without having to lock everytime. This can allow to run the audio thread on one processor while cooking up a big list on the other processor. Otherwise the non-audio thread will get blocked even though the audio thread never even looks at the symtable.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Le 2012-02-07 à 18:49:00, Mathieu Bouchard a écrit :
Sharing gensym is useful so that one can prepare lists and messages without having to lock everytime. This can allow to run the audio thread on one processor while cooking up a big list on the other processor. Otherwise the non-audio thread will get blocked even though the audio thread never even looks at the symtable.
... and part of what I say also applies to single-instance multi-thread, such as this paragraph.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Le 2012-01-16 à 09:43:00, IOhannes m zmoelnig a écrit :
what is wrong with eliminating all directly accessible globals from the API (like "pd_objectmaker") and provide accessor functions to get (thread safe) access to them?
Or just structs. You'd have a t_pdglobal type containing all the global variables that should never be shared.
And a separate section of the same struct could also list the global variables that could be shared, but might be not (e.g. if there's any reason to support both a single symtable and multiple symtables).
afaik, the usual way to accomplish a concept of multiple instances is to aggragate all currently global variables into a "context" and extend the API so that each function has an additional parameter for this context.
I mean that this context could be accessed directly if there's no reason to use accessors. But if locking has to be done before and after accessing (some of) those members, then it's nice to have a shortcut.
this will obviously break API compatibility... a possible workaround would be to add a new function "t_pdcontext*make_current(t_pdcontext*)" that would change the running instance and all API calls would henceforth work on the current context. this would still need a global variable (the current context). the "make_current" call could update the legacy global variables to contain the "current" values. (this would only work as long as no code caches the values of these variables).
how many of these variables are there ? That could be a lot of variables to be swapped around.
e.g. c++11 (c++? ever tried to compile Pd with a c++ compiler?)
I did. I don't recall significant difficulties... it might have been just the need to explicitly cast float to int, and something else involving void pointers... the job of making it compile with C++ wasn't a portion worth mentioning of the project I was working on (less than 1 %).
It took less time than just getting rid of all of pd's unused variables just so that I could see my own mistakes easily.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 2012-02-08 00:36, Mathieu Bouchard wrote:
I mean that this context could be accessed directly if there's no reason to use accessors. But if locking has to be done before and after accessing (some of) those members, then it's nice to have a shortcut.
another reason is, that with accessor-functions you can more easily stay binary compatible both backward and forward than with directly accessing the struct. sure you can do something like that with structs as well (typecast to different structs depending on the version of the Pd-host), but with accessors it comes for free (well, at least backwards compatibility)
this will obviously break API compatibility... a possible workaround would be to add a new function "t_pdcontext*make_current(t_pdcontext*)" that would change the running
how many of these variables are there ? That could be a lot of variables to be swapped around.
right, peter already convinced me, that having a single legacy context that is used directly is more appropriate.
e.g. c++11 (c++? ever tried to compile Pd with a c++ compiler?)
I did. I don't recall significant difficulties... it might have been
i guess i was exaggerating the use of C++ reserved keywords as variable names and the like. sure, this is easy to fix. but the original suggestion sounded to me like: 'a solution for all our problems is to switch to new C++ features in a "CC=g++ -fdo-what-i-want" manner', and i wanted to object to that.
fgamsdr IOhannes
Le 2012-02-08 à 09:08:00, IOhannes m zmoelnig a écrit :
another reason is, that with accessor-functions you can more easily stay binary compatible both backward and forward than with directly accessing the struct.
Depends what you think can be changing in the future...
sure you can do something like that with structs as well (typecast to different structs depending on the version of the Pd-host), but with accessors it comes for free (well, at least backwards compatibility)
ok... then why not accessors, then. fine.
Has anyone made a list of all (current) globals yet ?
e.g. c++11 (c++? ever tried to compile Pd with a c++ compiler?)
I did. I don't recall significant difficulties... it might have been
i guess i was exaggerating the use of C++ reserved keywords as variable names and the like.
C++ reserved keywords are easy to deal with. They're easy to search-and-replace, or #define away. Some other things are a bit more work, but that's quite tiny compared to most other cases of «translating» between very similar programming languages.
In the eighties, people spent a LOT more time porting software from one version of Microsoft BASIC to another version of Microsoft BASIC that was incompatible simply because it was on a different computer brand.
but the original suggestion sounded to me like: 'a solution for all our problems is to switch to new C++ features in a "CC=g++ -fdo-what-i-want" manner', and i wanted to object to that.
Well, a good reason to «object» to that is simply that C++11 implementations are not ready for use, and even if they were, they'd be so new that they wouldn't be bundled with Ubuntu, Debian, Fink, MinGW nor XCode.
Apart from the fact that this feature will not really cure the problem and will create new ones.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
On Sat, Jan 14, 2012 at 3:04 PM, Miller Puckette msp@ucsd.edu wrote:
To Pd dev -
For some time the good folks who brought us pdlib have been asking how one could make it possible to run several instances of Pd in a single address space.
Maybe I have on my audio-colored glasses--but that's just where I see things happening.
I remember a conversation with IOhannes in August about multi-threading audio via sub-canvas user interface object (propose thread~ akin to block~). If all you're after is audio multi-threading--there's no need for multiple instances of Pd. Threads could be used to start a portion of the dsp chain, running asynchronously, and then join/synchronize with Pd when finished.
What this would look like: Add a thread_prolog, thread_epilog, and thread_sync function. The thread_prolog function that occurs before block_prolog, starts a thread running the portion of dsp chain cointained within, and returns the pointer to the function following the thread_epilog. The thread_epilog function that occurs after block_epilog--waits for synchronization and returns.
What's the difficult part: You would need to have a good ordering of the dsp chain to take advantage of concurrency--each subcanvas having a thread~ object needs to kick off as early as possible, followed by objects that have no dependence on its output. Secondly, you'd need to put thread_sync on the dsp chain immediately before you will encounter functions with data dependencies.
What this approach would provide: a user interface to control audio threading, without having any duplication of global variables/arrays/symbols, etc. It would put the threading operations closer to the calculations to be performed, and so eliminate many problems as being out-of-scope.
I've been looking at asynchronous dsp chain operations for my PdCUDA project (which is several months behind where I expected to be)--the basic problem is the same. I haven't gotten that far yet, but someday I'm going to get to the point where improvements will be made by changing the order in which perform functions are placed on the dsp chain.
Maybe this doesn't fit what you want--so I'd refer back to problem definition. What's the point of threading and the usage case you have in mind?
Chuck
Hi Chuck, Check out the early bits of this thread --- various use cases already came up along the way: http://lists.puredata.info/pipermail/pd-dev/2012-01/017992.html. The short version is that libpd is being used in such a wide range of settings that you can come up with legitimate use cases for pretty much anything (single Pd instance shared between several threads, multiple Pd instances in one thread, and anything in between). At the level of the audio library, it's impossible to make good assumptions about threading.
I remember a conversation with IOhannes in August about multi-threading audio via sub-canvas user interface object (propose thread~ akin to block~). If all you're after is audio multi-threading--there's no need for multiple instances of Pd. Threads could be used to start a portion of the dsp chain, running asynchronously, and then join/synchronize with Pd when finished.
I don't think a patch is the place where decisions about threading should be made. Threading is an implementation detail that users shouldn't have to worry about, and besides, whether you have anything to gain from threading will depend on a number of factors that users won't necessarily be able to control or even know about.
What this would look like: Add a thread_prolog, thread_epilog, and thread_sync function. The thread_prolog function that occurs before block_prolog, starts a thread running the portion of dsp chain cointained within, and returns the pointer to the function following the thread_epilog. The thread_epilog function that occurs after block_epilog--waits for synchronization and returns.
What's the difficult part: You would need to have a good ordering of the dsp chain to take advantage of concurrency--each subcanvas having a thread~ object needs to kick off as early as possible, followed by objects that have no dependence on its output. Secondly, you'd need to put thread_sync on the dsp chain immediately before you will encounter functions with data dependencies.
I believe it's much simpler than that. It should be enough to just do a topological sort of the signal processing graph; that'll tell you which objects are ready to run at any given time, and then you can parallelize the invocation of their perform functions (or not, depending on how many processors are available). I don't think there's any need to explicitly synchronize much; tools like OpenMP should be able to handle this implicitly. Cheers, Peter
Le 2012-01-25 à 12:46:00, Peter Brinkmann a écrit :
Threading is an implementation detail that users shouldn't have to worry about,
If you sweep threading under the carpet, it makes the carpet turns into an evil mutant who will come back to eat you.
I warned you.
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Le 2012-01-14 à 13:04:00, Miller Puckette a écrit :
The question I'd like to rais is this: would it suffice to make Pd instances be per-thread?
This means I'd have to switch threads every time I want to send a message to pd. In my app, I have a main thread, and I have a pd-audio thread. The message-domain stuff currently runs in the main thread. I use either sys_lock or I switch to using a lock that's actually reentrant. This is easier than switching threads.
Alternately, message-passing can happen using «mailboxes», but that's more complicated, and it means not being able to get an answer from pd right now, because it's async.
Alternatively, one could use the C++11 standard thread_local keyword, although I believe that's not widely implemented yet.
AFAIK, this standard in general is hardly supported at the moment, not just the thread_local keyword, and I'd rather have Pd's source start using C++83 features before it starts getting into C++11 land.
To do this I'd replace all globals like static t_symbol *symhash[HASHSIZE]; with PDSTATIC t_symbol *symhash[HASHSIZE];
I'd rather have you put a separate lock inside gensym so that we don't have to call sys_lock and sys_unlock just for that. Then that hashtable can be shared between instances, in nearly all situations. Saves some RAM too.
- external objects making explicit references to global storage (such
as canvas_dspstate and cos-table in m_pd.h and much stuff in the more 'private' header files) would have to be recompiled to run per-thread. They'd still work fine with vanilla Pd.
You don't need multiple copies of the cos-table, and you won't ever need.
- supposing you use this to make a VST plug-in: what would happen if
some stupid host app called all its VST plug-ins from the same thread? (I think it's normal for hosts always to make a new thread for every VST plug-in instance but I don't know if this is universal).
Why is it normal to have one thread per plug-in ? As someone who doesn't know VST, this seems weird. Think of Pd as being a plugin interface of its own. Pd can schedule all of its plugins in the same thread and it's not a problem for working patches. It might be just some kind of infinite-loop-protection (a feature that Pd doesn't have).
or some declspec horror in Miscoroft C.
I don't know that name. Maybe you mean H.P.Lovecraft, which also ends with «ft».
______________________________________________________________________ | Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC