A signal pipeline runs at 4 microseconds a call for hours, then drops to 60 microseconds after a decay filter has run with no input for a long stretch. All inputs are finite and the code did not change. Name the cause, explain the mechanism, and give two fixes.
A signal pipeline runs at 4 microseconds a call for hours, then drops to 60 microseconds after a decay filter has run with no input for a long stretch. All inputs are finite and the code did not change. Name the cause, explain the mechanism, and give two fixes.
Approach: Ask what happens to the magnitude of an exponentially decaying state with no input, and what the hardware does once a value falls below the smallest normal number.
The filter state has decayed below the smallest normal double, about 2.2e-308, so every operation now runs on denormal values, which most processors handle through a microcode assist at 100 or more cycles each, giving the 15 times slowdown; fix it by setting flush to zero and denormals are zero in the floating point control register, or by clamping the filter state to zero once its magnitude falls under a threshold such as 1e-20. A denormal preserves precision near zero using a reduced significand, and the hardware fast paths generally do not cover that encoding, so the instruction traps to a slow path. Setting the control bits costs one instruction at thread start and changes results only at magnitudes no risk number depends on. The explicit clamp is portable, local to the filter, and documents the intent. Compiling with fast math sets those bits as a side effect, which is why the problem sometimes disappears for a reason nobody recorded.
Follow-up: Why does the same code show no slowdown on a build with fast math enabled, and what does that hide elsewhere in the pipeline?
Key concepts: denormal values, flush to zero, floating point control register, microcode assist.