Release Build Optimizes Out Important Code

Hi All,

I am working on an audio plug-in that I’ve noticed works when I run the Windows Debug build, but doesn’t pass audio through an important section of processing code on the release build. I am using Visual Studio 2022 to build the project. On Mac the Release builds seem to work.

This is the first time I’ve encountered this issue, so while I’m not 100% certain the optimizer is the problem, I can’t see what else this might be given that the Debug works and the Release doesn’t.

Does anyone have experience debugging this kind of issue? Are there things in the code that I can watch out for that might be causing this??

Thank you!!

One difference is uninitialised variables. In a debug build they are filled with magic numbers that might happen to allow the program to run determinalistically. In a release build uninitialised memory is indeed random.

Another difference is a race condition. Fabian explained it best in the ADC talk Realtime 101. A missing atomic hint for the optimiser lets it assume a variable will never change and remove or pre-empt calculations with that value. If the optimiser knew, this variable can change from a different thread, it would not have come to that false assumption.

Just as a starting point, there may be more things which could go wrong, impossible to say without actual code.

1 Like

thanks I’ll take a look at these options and see if there are any hints, thank you!

Looks like this was indeed due to a data race. Thanks Daniel!

1 Like

Wow, is this anything you could share? I’d be really curious to see a real world example of that.

Unfortunately I can’t share the exact code but I can try to explain what happened:

I had a block in my code that would run if the user had selected to run the plugin in “mono” but the buffer provided still had two channels (which happens in certain DAWs) So something like…

//declared in header 
int channelSetting { 0 }; 


//deep inside processBlock().....(on the Audio thread, importantly)
if (getChannelSetting() == 1 && getMainBusNumInputChannels() == 2) 
{
 //do a bunch of stuff to account for this particular condition
}
 //then execute the rest of the processBlock() code

where getChannelSetting was something like

int getChannelSetting() 
{
 return channelSetting;
}

But I found that this worked in Debug builds and when the optimization was disabled, but not Release builds compiled with optimization.

The problem here (*I think!!) was that getChannelSetting() returned a non-atomic member int which was set elsewhere on the message thread in something like

void setChannelSetting (int newSetting)
{
 channelSetting = newSetting;
}

which is cleary a race condition when setChannelSetting() could be called from the message thread, and getChannelSetting() could be called from the audio thread. One thread could try to write to and the other could try to read from the member int channelSetting simultaneously.

The solution (or one solution at least) was to make the member channelSetting a std::atomic<int>, and re-write the code as follows:

//declared in header...
std::atomic<int> channelSetting { 0 }; 

//setters/getters
void setChannelSetting (int newSetting)
{
 channelSetting.store(newSetting);
}

int getChannelSetting() 
{
 return channelSetting.load();
}

//deep inside processBlock().....(on the Audio thread, importantly)
const int cs = getChannelSetting();
if (cs == 1 && getMainBusNumInputChannels() == 2) 
{
 //do a bunch of stuff to account for this particular condition
}
 //then execute the rest of the processBlock() code

So I think the optimizer assumed that the channel setting was ALWAYS 0 since it was initialized to 0, and since it never thought it could be changed from another thread, so that original condition never passed. My understanding is that std::atomic lets the compiler/optimizer know not to make this assumption, but I know nothing about how that actually works.

Hopefully that makes some sense!

1 Like

Okay, that all makes sense. Thank you so much for taking the time to explain and not being afraid to share a mistake that we can all learn from.

…and, excellent diagnosis, Daniel!

3 Likes