…actually, looking at it again, I think I was using the wrong data structure. I’ve changed it now to use a std::unordered_map, which I think should make it really smooth - would be interested in how your pathological case performs…
Here’s a profile of the init section of my plugin where the Standalone App instantiates my Processor and it initializes the Mixer, etc… We can see that your new buildRenderingSequence() is slower than the original:
Which leads to if I only build the RenderSequenceFloat in buildRenderingSequence() I beat or equal my code’s speed*… perhaps if we could figure out the AudioBuffer FloatType we could set a flag for the graph??
*If you replace the MessageManagerLock in buildRenderingSequence() with a ScopedLock… otherwise you get a performance hit (around 18%)… I haven’t run into any issues with the change.
So other than the previous changes I had to make I had to change AudioProcessorGraph:: getConnection() (which I use for debugging to log the graph) to:
Definition:
/** Returns a pointer to one of the connections in the graph. */
const Connection* getConnection (int index) const;
Hmm. I wonder if I just need to re-think the whole approach and give the nodes pointers to their connection objects rather than storing the connections in a container. That could magically speed up all the hotspots that you’re hitting… I might give that a try later.
Just to chime in here a little bit: I also made my own version of the AudioProcessorGraph. I needed the ability to update to changing latencies as well. I did have to get rid of triggerAsyncUpdate, it just didn’t work for me like that, because I need reliable timing on graph changes, so I used a system with a timer and added the option to accumulate and postpone graph updates for a while (when patches change).
In addition I needed a way to prevent the user from creating loops in the graph. To detect a potential loop before it gets added, I used the boost graph library and one of it’s graph traversal search algorithms. Such a graph library could also be used to create rendering sequences and I believe performance could be much better/consistent that way. IMHO the buildRenderingSequence suffers from too much iteration and recursive calls.
So I just tested the latest tip with the new code… and the graph works… but changing connections seems slower (definitely a lot slower in debug due to the recursion in isAnInputTo()… but changing or adding connections appear to be quite a bit slower than what I was using (in Release)… I’ll make some time in the next few days to run profile comparisons.
Still seeing this… with float only… and changing the MessageManagerLock to a ScopedLock… we can profile and see the biggest bottleneck is the recursion in createOrderedNodeList()
Now this is interesting… I’m comparing what is basically your experimental code base prior to the tip release code…
In my Plug-in which can host other plug-ins… you can drag the internal effects around to change their order… this changes some connections in the graph… now if we look at the profile for the new tip code:
and the previous (cleanup_audiograph) based code profile:
We can see that your previous code is a lot more efficient.
Between your changes and mine, we’ve tried a lot of different approaches to this and they’ll all have different performance characteristics with different use-cases! I’m really slammed this week but will try to come back to have a look at this when I get chance.
Thanks… More importantly, I’ve just run into a scenario which happens in both your cleanup_audiograph and the tip… but not in the older AudioProcessorGraph… where I’m getting one of the processors in the graph leaking. I have a reproducible case to cause the issue… but I’m still trying to isolate the exact cause of the leak (why is one of the processors not getting deleted when the graph is deleted)… when I list my connections they appear the same as in the original graph except for the MIDI connections…
None of these is related to the leaking processor though, where the log is the same on both versions (it’s Node 79’s processor which is not being deleted):
I’ll let you know if I can isolate the issue. The only thing I see which is off in the trigger is the source connection to node 78 (Synth Processor) changes in the log… where it shouldn’t change.
The answer may be to allow a graph to be a child of another graph… allowing MIDI in and out, etc. That would let us reduce the number of connections needing to be resorted.