Через несколько секунд выполнение метода занимает больше времени

У меня есть этот метод:

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;
}

это работает... вроде, единственная проблема в том, что я получаю много искажений на высоких частотах. какой метод интерполяции вы можете предложить?

Другие вопросы по тегам