几秒钟后,一个方法需要更长的时间来执行

时间:2013-01-13 20:46:37

标签: java optimization audio sine

我有这个方法:

public double sineWave(double t) 
{
   return amplitude==0?0:Math.sin(t * frequency * Math.PI*2 + phase) * amplitude;
}

它由另一个类中的另一个方法调用,以生成简单正弦波的样本,然后将其添加到缓冲区中以发送到声卡。 t是时候了。由于某种原因,应用程序调用此方法越多,它获得的速度就越慢。它没有任何意义,在15秒之后,它足够慢,可以使用我的CPU的完整内核并使音频断断续续。

我100%肯定这是一段代码,因为如果我用返回0替换它,运行它所花费的时间(用System.nanotime()测量)是不变的。

为什么会这样?我有什么办法可以解决这个问题吗?

4 个答案:

答案 0 :(得分:5)

根据这里的信息 - 虽然不清楚缓冲区有多大,但每次迭代都会增加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

答案 1 :(得分:0)

请不要给出任何要点,因此解决方案将得到@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;
}

答案 2 :(得分:0)

我用查找表解决了这个问题:

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

它有效......有点问题,唯一的问题是我在高频上会受到很多的歪曲。有什么插值方法可以建议吗?

答案 3 :(得分:0)

Java框架的当前版本将尝试使用数学上完美的2π值而不是值Math.sin将参数mod修改为Math.PI*2。对于像你这样的代码,这意味着与使用与乘法中使用的相同比例因子(即Math.PI*2)执行mod减少相比,代码将花费更长时间并产生更不准确的结果。为了获得良好的准确性和速度,您应该在进行乘法之前执行模数减少,使用类似:

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
}

这将确保sincos都不会使用大于π/ 4的参数,这是根据文档中Java切换到使用缓慢和适得其反的范围缩减的点。