Cyto's Guide to Subnormals

Until our dedicated user library is in place you can post examples and modules here

Moderators: electrogear, exonerate

Cyto's Guide to Subnormals

Postby cyto on Fri Jul 13, 2012 4:13 am

The topic of subnormals comes up a lot in “trouble-shooting” threads. I thought it might be a good idea to present here, in one thread, all the information relevant to the topic. Hopefully, this will make it easier for users who experience the dreaded “subnormal spike” to find a cohesive guide to tracking down and preventing subnormals. This initial post will be kind of a “FAQ” based on my experience but I'm hoping that others will contribute to the thread to offer their own expertise and insights. Throughout the FAQ, I have included various examples and code snippets (that you can cut and paste into your work) that should make wiping out these pesky little buggers a relatively painless endeavor.

Note: I use the term “subnormal”, but a lot of people use the term “denormal” or “de-normal” or “denormalized number”. Any term is acceptable, but know that they all mean the same thing.

What Is A Subnormal?
The short answer: They are VERY small numbers. Small as in a decimal point followed by 38 zeroes and then some non-zero digits.

The long answer: In the float32 decimal representation, the first bit is the sign bit, the next 8 bits are the exponent bits and the last 23 bits are the mantissa bits. The number, then, is [sign * 2^exponent * mantissa]. The exponent has a lower limit of -127. The mantissa, while only 23 bits long can actually store 24 bit information. This is done with the use of an “implied leading one”. So instead of mantissas of 0 to 1, you actually have mantissa values of 1 to 2. The CPU knows to add a leading one to the value. In order to allow values lower than what the -127 exponent would allow, the processor can go into “denormal mode,” effectively ignoring the leading one. While accuracy at those low levels may be maintained, the CPU cannot perform it math as normal (pun intended). Instead, it uses subnormal-specific instructions that are sometimes 100 times slower than normal.

Why Are Subnormals Bad?
As stated before, they require A LOT of CPU resources. While any good DSP will strive for utmost accuracy, these values represent signal levels below -800dBFS. They simply aren't significant enough to warrant the extreme CPU demands to deal with them.

What Causes Subnormals?
The most common cause of subnormals are delay lines where the signal is attenuated by multiplication (or division) and fed back into itself. This happens obviously in in echo/delay/reverb effects, but more often than not, it occurs in IIR filters (which have one or more “feedback arms”). Consider the following example (a simple 1st order Highpass Filter):

Code: Select all
out = in – in1 + (out*coeff);
in1 = in;

The feedback arm (out*coeff) will constantly multiply the result of the previous instance by the coefficient (a positive value less than 1). This is perfectly fine when a signal is present, but what happens when the input signal disappears? It keeps getting smaller and smaller and smaller and smaller, approaching but never reaching 0. Considering most audio is processed tens of thousands of time per second, it isn't long before you reach that 1e-038 threshold. When this happens the CPU must enter denormal mode.

How Can I Test For Subnormals?
The easiest way is to just look at the CPU meter at the bottom of Synthmaker. If you notice that it is higher after you release a note (or send audio through) than when a signal is present, then you have a subnormal problem. This may be harder to see on newer, faster CPUs (most subnormal spikes on my quad-core i5 only register at about 5%), but if you're looking for it, you'll see it. I always like to check my work on my atom machine. That thing will jump to 100% CPU if subnormals are present.

There are other ways with some analytic tools floating around, but often subnormal values are “trapped” in feedback loops in the code blocks which they originate, so the output might not necessarily show the presence of the subnormals.

I Know I Have Subnormals, How Do I Track Them Down?
First, examine anything that has a feedback loop in it. 99% of the time, that's where they'll be: Delays, reverbs, filters, etc. To test, simply disconnect the suspected module (or comment out the block of code) and see if you notice any performance improvement after notes have been released. If so, you've found the culprit. If not, keep looking!

It's best to look in the “mono” section of your project (with the blue connectors). This section is always active and therefore very prone to subnormals. While subnormals will show up in the poly section, they are not as obvious (and not as detrimental) because the poly sections “shut off” when the ADSR envelope closes.

Okay, I've Tracked Them Down, How Do I Deal With Them?
There are three common ways to deal with subnormals. We'll call them the “noise”, “redundant math”, and “test and flush” methods.

The Noise Method
The Noise method introduces very low level noise near the beginning of the audio path. This doesn't really get rid of them, it just makes sure they are never present to begin with. It ensures that there is always a signal present so the feedback arms never get the chance to enter subnormal levels. The “denormal remover” module in the SM toolbox has this option, but it uses a “white noise” routine to do so. Personally, I believe this to be overkill, so here is a little piece of code that you can paste into a “code” or “assembler” block to achieve the same results. It simply produces a low level signal at Nyquist that should be sufficient to prevent denormals, while still being way below the audible level. This method has the advantage of being easy to implement. You simply insert near the front of your audio path and it should take care of the problem. You might run into problems if you happen to attenuate this signal before it reaches the problem area, though. So you may need to play with the position of the code in the schematic path or the level of the noise. The downside of this method is it does introduce noise into the schematic and may or may not affect the sound you're after (depending on how sensitive you are to that kind of thing).

Code: Select all
streamin in;
streamout out;
float hold;
stage(0){
hold = 1e-024;
}
stage(2){
hold = -1 * hold;
out = in + hold;
}


Code: Select all
streamin in;
streamout out;
float negOne=-1.0;
float level=1e-024;
stage0;
movaps xmm0,level;
movaps hold,xmm0;
stage2;
movaps xmm0,hold;
mulps xmm0,negOne;
movaps hold,xmm0;
addps xmm0,in;
movaps out,xmm0;


The Redundant Math Method
The second method the deal with subnormals is to simply perform a redundant math operation. The theory behind this is if you have a subnormal value and you add it to, say, 1 and then immediately subtract 1, the subnormal value is “eaten up” in the operation. To implement that in our earlier HPF example you would do something like...

Code: Select all
temp = in – in1 + (out*coeff);
out = temp + 1 – 1;
in1 = in;

The trick here is to find an appropriate value to add and subtract. In much the same way that subnormals are “eaten up” so may other values that are drastically different from the value you choose. Some have suggested that a value of 1e-012 is a good starting point. But all situations are different and you may need to experiment with different add/subtract values. I used to use this method all the time because it is pretty easy to implement, but rarely do anymore because it can seriously affect the “resolution” of your filters.

The Test and Flush Method
The third and final method I'll discuss is my preferred method. It involves simply testing for values that may be approaching the subnormal level and flushing them to zero. I personally have come to favor the threshold value of 1e-036 as it is pretty insignificant to begin with but leaves a little “wiggle room” for a few more multiplications before you start getting subnormals. To implement it in our HPF example, I would do something like...

Code: Select all
temp = in – in1 + (out*coeff);
out = temp&(abs(temp)>1e-036);
in1 = in;

By using the AND logic with the condition that the absolute value of the output is higher than my chosen threshold, it ensures that any value approaching (or having reached) subnormal will automatically become a zero and any value higher than the threshold will go through unchanged. In assembly, you could do the same thing like this...

Code: Select all
//define these values
float subNorm=1e-036;
int mask=2147483647;

//value to test is in xmm0
movaps xmm7,xmm0;
andps xmm7,mask;
cmpps xmm7,subNorm,6;
andps xmm0,xmm7;
movaps out,xmm0;

An important thing to point out... if you are using higher order filter (like biquads or similar) that have multiple feedback arms in series, you don't need to perform these actions for every value passed to the next instance. Generally, if you wipe out subnormals in the first feedback arm, that should effectively take care of them for the subsequent ones.

Okay, I hope that helps people understand subnormals a little better and I hope some of the examples I gave provide a good foundation of tools and tricks to help optimize your projects. Anybody who pays attention probably knows that I struggled with a lot of these issues when I was first starting with SM and had some crazy notions of how to handle these situations. Thanks to many people on here (trogluddite(!) , aliasant, infuzion come to mind) I think I've gotten the hang of it. Hopefully, this thread will provide a one-stop place for people in the same situation.

If I have mis-stated or omitted anything, please chime in so I can correct it. Any other comments or additions as always are welcomed and encouraged! :)

-cyto
User avatar
cyto
essemilian
 
Posts: 317
Joined: Sun Nov 28, 2010 4:36 am
Location: CIN | OH | USA

Re: Cyto's Guide to Subnormals

Postby pall on Fri Jul 13, 2012 9:21 am

I did not know why many times, the CPU is higher when not is signal. So because denormals?!
It's good to know this.
Thank you for this great work, cyto! :)
It can make few modules with this codes?
pall
essemilian
 
Posts: 305
Joined: Thu Dec 09, 2010 12:27 pm
Location: Transilvania

Re: Cyto's Guide to Subnormals

Postby martinvicanek on Fri Jul 13, 2012 4:45 pm

Great post, Cyto! All you ever need to know about subnormals!
thumb.png
thumb.png (13.04 KiB) Viewed 1301 times
martinvicanek
essemilian
 
Posts: 308
Joined: Sun Mar 13, 2011 1:15 pm

Re: Cyto's Guide to Subnormals

Postby philter5 on Fri Jul 13, 2012 6:49 pm

very enlightening :o . like pall, i often wondered myself about this behaviour with the higher CPU after
note is released.thx cyto ! ;)
---Yes, a piece of software CAN be your best friend---
User avatar
philter5
smaniac
 
Posts: 1480
Joined: Thu Jan 04, 2007 7:52 pm
Location: Germany

Re: Cyto's Guide to Subnormals

Postby CoreStyler on Sun Jul 15, 2012 7:19 am

Cyto add that to synthmakers.net wiki!
http://www.thecorestylerz.net
Sound Design, synth development and websites building...
Image
SM COMMUNITY IS MOVING TO
www.synthmakers.net
User avatar
CoreStyler
essemilian
 
Posts: 474
Joined: Sun May 23, 2010 1:25 pm

Re: Cyto's Guide to Subnormals

Postby infuzion on Sat Jul 21, 2012 5:16 am

CoreStyler wrote:Cyto add that to synthmakers.net wiki!

+1
Need help? First search the forum & WiKi, then post in the help forum with a clear topic, request, & OSM. Then please WiKi the correct solution. If you want my personal assistance, I charge by the hour or for an exchange of services.
infuzion
smstar
smstar
 
Posts: 6163
Joined: Wed May 04, 2005 8:02 pm
Location: Earth, USA, CO, Denver


Return to Examples

Who is online

Users browsing this forum: Google [Bot], RJHollins and 3 guests