How to avoid allocation of dynamic objects on audio thread


#1

Hey Community,

So for the past couple of weeks I have been doing research on the topic. I have read couple of similar disscusion and the subject still baffles me, so I thought I would throw the question here and see if somebody has an insight.

for simplicity lets say I am doing the following in processBlock:

         dynamicObject->ProcessingStereo(buffer.getReadPointer(0), buffer.getReadPointer(1), buffer.getWritePointer (0), buffer.getWritePointer (1), buffer.getNumSamples());

now lets say we want to reallocate dynamicObject, ok we cant do it on audio thread (real-time audio waits for nothing, new and delete are actually blocking instructions and all that), so how would you ensure thread-safety when allocating dynamicObject on GUI thread (or any other thread)?

my Idea is to call this function from GUI, and define a bool flag letting GUI-thread know that something has changed in audio-thread:

in processor I define:

    void reloadObject() {
      delete dynamicObject;
      dynamicObject = new DynamicObjectType;
    }

    void checkIfSettingsChanged() {
       // if something has changed set
       settingsChanged = true
    }

and back in processBlock, I modify as follows:

        checkIfSettingsChanged();

        if (settingsChanged) {// just deliver zeros, when we are reallocating stuff

             for (int channel = 0; channel < getTotalNumInputChannels(); ++channel)
            {
                float* channelData = buffer.getWritePointer (channel);
            
                for (int b = 0; b < buffer.getNumSamples(); ++b) {
                
                    channelData[b] = 0;
                
                 }
               }
             }
         } else {

               dynamicObject->ProcessingStereo(buffer.getReadPointer(0), buffer.getReadPointer(1), buffer.getWritePointer (0), buffer.getWritePointer (1), buffer.getNumSamples());
         }

meanwhile in GUI thread ( or Editor) we have a timerCallback that does the following:

  void timerCallBack() {
          if (settingsChanged) {
               getProcessor().reloadObject(); 
               settingsChanged = false;
          } 
  } 

taking into consideration that I am defining settingsChanged as atomic, is this a safe design? or am I going to have issues with memory synchronization or tearing of any kind.


#2

First, you are using bad delete calls, they don’t match the new. Use std::unique_ptr instead.
Then it’s a mixed bag. Your object generates one sample at a time, and you are trying to use a lock free method for performance. Doesn’t sound right. Ask for all your samples in process(), not just one, and then it may be consistent.


#3

Hey Mathew,

thanking you for pointing out the mistakes, I just wrote the code snippet by heart, since my point wasnt syntax. The code was meant to be pseudo. But anyway, back to the actual question, Am I understanding the multi-threading behavior correctly?

PS: I fixed the stuff you mentioned, so that people dont get confused


#4

Hi tetrohed,

you can allocate the needed memory for dynamicObject beforehand and use this each time you (re)create it by using placement new instead of ordinary new, like

new (preallocatedMem) DynamicObject()

instead of

    new DynamicObject

More at https://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new


#5

But don’t forget to call the destructor and don’t allocate data in your constructor.


#6

I’m working on an EQ where this problem came up. I wrote a “MemoryManager” template class that handles construction/destruction of all the objects on its own thread, but pre-allocates the objects ahead of time so requesting a “new” object instance or “deleting” it was just swapping pointers as far as the Processor was concerned. Once I had that working, getting the memory manager to recycle object instances instead of destroying them was pretty trivial, which minimizes the allocation overhead when you’re creating/deleting objects on the fly.

Something I’ve been meaning to work out is creating a MemoryManagedObject base class which takes a reference to a MemoryManager instance in its constructor, then override the new/delete operators.


#7

I wonder if this is still thread safe. Even if changing pointers happens fast, we would have undefined behavior. e.g answer this question: your memory manager just exchanged pointers, in the meantime the processor accesses the pointer, which one does it access? the old one or the new one?


#8

You have the access to the pointer after the check on the boolean. And only the main loop can change the value to true, so while you are in the loop, the pointer will be good. The timer won’t be able to change it.
If the boolean is set to true, than you don’t touch the pointer, so when the callback runs, you can change the pointer.

I think it’s OK.


#9

Thanks Matthieu and everyone else for the inputs, I think I have a grasp on the topic now


#10

In general, if you use gcc or clang, compile with “-fsanitize=thread”, then you will get a runtime error if it isn’t safe.

(There is also the tool “helgrind”, part of valgrind, that checks for the same things, but -fsanitize=thread should be much faster. Valgrind is often too slow for dsp processing)