Через несколько секунд выполнение метода занимает больше времени
У меня есть этот метод:
public double sineWave(double t)
{
return amplitude==0?0:Math.sin(t * frequency * Math.PI*2 + phase) * amplitude;
}
Он вызывается другим методом в другом классе для генерации образца простой синусоидальной волны, которая затем добавляется в буфер для отправки на звуковую карту. t
это время. По какой-то причине, чем больше приложение вызывает этот метод, тем медленнее он становится. Это просто не имеет смысла, после 15 секунд это достаточно медленно, чтобы использовать полное ядро моего процессора и заставить звук заикаться.
Я уверен на 100%, что это этот кусок кода, потому что если я заменю его на возвращаемый 0, время, необходимое для его запуска (измеряется с System.nanotime()
) постоянно.
Почему это происходит? Что я могу сделать, чтобы это исправить?
4 ответа
Из приведенной здесь информации - пока неясно, насколько велик ваш буфер, вы увеличиваете t с каждой итерацией. Предполагая, что ваша частота достаточно высока, вы увеличиваете аргумент Sin() с каждой итерацией. Имейте проверки, чтобы видеть, постоянно ли увеличивается аргумент до очень высокого значения. Быстрый и грязный тест показывает, что производительность Sin снижается -
public class SinTest {
public static void main(String args[]) {
long angle = Long.parseLong(args[0]);
long startTime = System.nanoTime();
for(long l=0L; l<=1000000L; l++) {
Math.sin(angle);
}
long estimatedTime = System.nanoTime() - startTime;
System.out.println(estimatedTime);
}
}
$ java SinTest 100000
29181000
$ java SinTest 10000000
138598000
Пожалуйста, не дайте очков, поэтому решение будет дан ответ @mk:
public double sineWave(double t)
{
final double TAU = Math.PI *2;
double a = t * frequency;
a -= (long)a;
return amplitude==0?0:Math.sin(a * TAU + phase) * amplitude;
}
Текущие версии платформы Java будут пытаться изменить аргумент до Math.sin
используя математически совершенное значение 2π, а не значение Math.PI*2
, Для такого кода, как ваш, это означает, что код займет больше времени и даст менее точные результаты, чем если бы уменьшение мода было выполнено с использованием того же масштабного коэффициента, который использовался при умножении (т.е. Math.PI*2
). Чтобы получить хорошую точность и скорость, вы должны выполнить уменьшение по модулю перед умножением, используя что-то вроде:
double thisSpin = t * frequency;
thisSpin -= (thisSpin - Math.Floor(thisSpin)) * 8.0; // value of 0-7.9999=one rotation
switch((int)(thisSpin*8.0))
{
case 0: return Math.sin( thisSpin * (Math.PI/4.0));
case 1: return Math.cos((2-thisSpin) * (Math.PI/4.0));
case 2: return Math.cos((thisSpin-2) * (Math.PI/4.0));
case 3: return Math.sin((4-thisSpin) * (Math.PI/4.0));
case 4: return -Math.sin((thisSpin-4) * (Math.PI/4.0));
case 5: return -Math.cos((6-thisSpin) * (Math.PI/4.0));
case 6: return -Math.cos((thisSpin-6) * (Math.PI/4.0));
case 7: return -Math.sin((8-thisSpin) * (Math.PI/4.0));
default: return 0; // Shouldn't be possible, but thisSpin==8 would be congruent to 0
}
Это гарантирует, что ни sin
ни cos
когда-либо используется с аргументом больше π/4, что, согласно документации, является точкой, в которой Java переключается на медленное и непродуктивное сокращение диапазона.
Я решил проблему с таблицей поиска:
private static final int LUT_SIZE=1000000;
private static double[] sineLookupTable=new double[(int)(Math.PI*2*LUT_SIZE)];
static{
for(double i=0;i<sineLookupTable.length;i++){
sineLookupTable[(int)i]=Math.sin(i/(double)LUT_SIZE);
}
}
private static double sinLUT(double t){
return sineLookupTable[(int) (((long) Math.floor((t%Math.PI*2)*LUT_SIZE))%sineLookupTable.length)];
}
public double sineWave(double t) {
return amplitude==0?0:sinLUT(t * frequency * Math.PI*2 + phase) * amplitude;
}
это работает... вроде, единственная проблема в том, что я получаю много искажений на высоких частотах. какой метод интерполяции вы можете предложить?