Performance problem / gui rendering


#1

I just finished to reprogram my live software using juce
the synthesis part uses about 2 to 10% cpu
each time i maximize / minimize a windows the cpu jump to 50% and more, leading to severe audio glitch,
of course not really acceptable in a live performance XD

it seems something related to gui rendering is eating much of the cpu
any of you guys had that problem, and eventually got a fast workaround ?

more details :
i have a decent computer, should not be the problem (q9550 ,ssd , 4 g ram win7 6x64)
the problem doesn’t show with other softwares (tried different daw, fruityloops with a lot of synth on the screen, etc)
my gui has quite a lot of components ->8 tracks, each one has a 32 steps sequencer and 6 rotary sliders, 4 horizontal sliders, and 2 switch buttons
the problem appears in debug or release builds, no difference
the problem is 100% reproducible, each time i maximize/minimize a windows, i get the problem (even if the windows is not on the same screen as the software)

my guess here is windows send a message forcing a full gui redraw but i could be plain wrong, since i don’t really have time to read the gui code atm,
i’m asking for direction, where should i search in your opinion ?


#2

The audio thread usually has a higher priority than the GUI thread. Have you made sure that your audio thread does not call any malloc, free, operator new, operator delete, std::vector, std::list (or any other container) insertion or removal functions, or call any JUCE, operating system, standard library, external library, or internal code that could cause memory allocation or deallocation, or acquiring a lock / CriticalSection?


#3

nearly everything is preallocated,
there are rare cases when allocation happens in the audio thread , (when changing an effect for example) which obviously doesn’t happen in my current situation.

there are 2 MessageManagerLock in the audio thread, one the oscilloscope view , one for the sequencer position display
i removed both as a test, it now only happen 20% of the time (debug build)

i guess that was playing a part in the problem

so now, what should i do ? send an async message with a broadcaster to call repaint() on those component?

Edit: on a side note , i take any good advice to increase the gui performance to the max


#4

Well for starters you should be running your Release build with full optimizations!


#5

Damnit i tried the release build, not much difference, suddenly i remembered one last thing was updated in the audio thread, the cpu % …
removed it, works flawlessly ,
still it’s insane how a simple juce::Label updated every 20 audio block can cause so serious audio dropout

the last version was written with delphi , the audio synthesis was awfully slow in comparison as you can imagine, and i was updating the same things in the audio thread and never had a problem !

so now i have a question , the cpu use was updated that way :

{
     MessageManagerLock MMl;
     CtrlPanel->LblCpu->setText("CPU : "+String(AudioDeviceMgr.getCpuUsage()*100.0,4),false);
}

if i remove the lock, i still have the problem, i see that setText internally call repaint()
in juce documentation it’s clearly stated that repaint() “Marks the whole component as needing to be redrawn.”,
not that the drawing is actually done, so what is really the problem here?


#6

Don’t access any GUI-related objects in the Audio-Thread! Try to rethink your design.


#7

Don’t do any MessageManagerlocks or such mess. Use the AsyncUpdater class when you can, Timers if you like that approach, but don’t lock unless you have to and only for Audio/MIDI reasons, never GUI reasons.


#8

nothing to change on design side luckily, i just stopped updating the gui in the audio thread
i will not use anymore any gui object in the audio thread, lesson learned

still that doesn’t explain why only updating the text of a label can provoke audio dropout since i tried without acquiring lock
i’ll investigate that later, i hate to do something without understanding the real problem

thank you for your help !


#9

malloc(), free(), operator new(), operator delete() take a global CriticalSection.

All gui operations make heavy use of the heap.

If you want to see how to efficiently update the GUI, have a look at the deck volume indicator in the “SimpleDJ” applet of AppletJUCE:

AppletJUCE


#10

What i can say without the technical know-how that i know others will say. Any GUI stuff is hundreds times slower then the Audio needs to be, so a simple lock that waits for a Label to re-paint is a giant glitch in the Audio process. That’s why you just need to signal your GUI to update and let it do it on the GUI thread and leave the audio running, same goes for MIDI (i work with MIDI only, but i guess the rules are the same).


#11

Yep exactly.

When I design concurrent systems I use the Observer pattern so that the real time section doesn’t even know about the GUI. They are in fact separated at many levels, at the bottom most level (or top depending on how you see things) the real time section doesn’t even have access to the include directories used to create the GUI.

SimpleDJ shows the usage of the Observer pattern. JUCE has its own form of observer (juce::ListenerList) although you have to built inter-thread signaling on top of that. AsyncUpdater is one of those building blocks.

Its okay to call AsyncUpdater::triggerAsyncUpdate() from within your audio thread. Ideally you would only call it once.


#12

I sometimes do locking in the audio thread but only to pass pre-allocated data (MIDI messages) to other threads, those thread also have pre-allocated memory segments so it’s just setting values in memory no allocation of freeing, theese locks are very quick, dunno if it’s ok but i see no other good way of passing data thread to thread, unless you involve some other IPC like FIFO or SHM


#13

[quote=“TheVinn”]malloc(), free(), operator new(), operator delete() take a global CriticalSection.

All gui operations make heavy use of the heap.

If you want to see how to efficiently update the GUI, have a look at the deck volume indicator in the “SimpleDJ” applet of AppletJUCE:

AppletJUCE[/quote]

[quote]SimpleDJ shows the usage of the Observer pattern. JUCE has its own form of observer (juce::ListenerList) although you have to built inter-thread signaling on top of that. AsyncUpdater is one of those building blocks.

Its okay to call AsyncUpdater::triggerAsyncUpdate() from within your audio thread. Ideally you would only call it once.[/quote]

i read the code, simple and efficient i like it.

Just one concern, in the case of the “SimpleDJ” program, you don’t have much to update, that would be nice if we could pass a “message” with each request, i guess a simple int , to be able to update one specific component


#14

It already works that way. You’re not thinking Observer pattern (http://en.wikipedia.org/wiki/Observer_pattern), you’re stuck in the imperative thinking model.

Every time the a Deck is done processing a block it calculates the overall output level and broadcasts the message to anybody who cares:

Deck.cpp

    // Notify listeners.
    m_listeners.update (&Listener::onDeckLevels, this, newLevels);

The CDeckLevelMeter is a Component which listens for changes in the deck output level and calls repaint() accordingly:

CDeckLevelMeter.cpp

class CDeckLevelMeter
  : public CLevelMeter
  , public Deck::Listener

Any number of additional Component objects can also be Deck::Listener objects. Note that this works almost exactly like the juce::ListenerList, except that you can specify which thread you want to be called on:

CDeckLevelMeter.cpp

  m_deck->addListener (this, vf::MessageThread::getInstance ());

It is true that the Deck::Listener is pretty simple:

Deck.h

  class Listener
  {
  public:
    virtual void onDeckLevels (Deck* deck, Levels level) { }
    virtual void onDeckSelect (Deck* deck, Playable::Ptr playable) { }
  };

You can always add more to it though, and with any parameters you want (up to 8 ). You can also have more than one Listener type. The system is flexible enough to support any use-cases.

The whole point of the Observer pattern is that the person sending the message doesn’t know who the recipients are. The audio thread doesn’t “choose” which Component to update.


#15

mmm ok, i misunderstood one part of the code obviously
But indeed you’re rigth i’m not much an OOP guru :wink: