|
Shedding some light on high performance.
Copyright: Dr Alexander J Turner - all rights reserved. |
|
Math.sin and the other transcendental functions in the JDKs java.lang.Math package tend to be very slow indeed. There are fast workarounds however.
The reason for the slowness is consistence. The library is designed to produce exactly the same results on all platforms. The results are standards compliant to the last binary bit. This is just great if that is what you need. However, for some applications, like audio synthesis for example, it is total overkill.
In DSP outboard processors, wavetables have been used for years. The idea being that is you pre-compute a set of values for a wave against phase angle. Then, when a value is required, it's offset into the pre-computed table is used to get the value straight away. Sonic-Field stores audio at float
(32 bit floating point) precision. A table big enough to hold all the possible values for the sin function where the phase angle is a float
is way to big to be practical. It turns out that we really do not require anything like as big a table as that. A relatively modest table with linear interpolation gives results which are within float rounding error of the Math.sin
values. The results are plenty good enough for audio work:
static double[] table = null;
static double step;
static double invStep;
static int size = 0;
static
{
size = 10000;
table = new double[size];
step = 2d * Math.PI / size;
invStep = 1.0f / step;
for (int i = 0; i < size; ++i)
{
table[i] = Math.sin(step * i);
}
}
private final static double pi2 = PI * 2;
final public static double sin(double ang)
{
double t = ang % pi2;
int indexA = (int) (t / step);
int indexB = indexA + 1;
if (indexB >= size) return table[indexA];
double a = table[indexA];
return a + (table[indexB] - a) * (t - (indexA * step)) * invStep;
}
The above code is straight from Sonic Field (and is therefore AGPL v3.0). It creates a 10 thousand element table. I tried a bigger table but it did not improve the results noticeably though less did degrade the accuracy rapidly. I have not micro-benchmarked the code, but Sonic Field patches using this code show the functions using sin running up to 10 times faster than using the Math.sin
function.