简体   繁体   English

几秒钟后,一种方法需要更长的时间才能执行

[英]After a few seconds, a method takes longer to execute

I have this method: 我有这种方法:

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

It is called by another method in another class to generate a sample of a simple sine wave, which is then added in a buffer to send to the sound card. 另一个类中的另一个方法调用它来生成简单正弦波的样本,然后将其添加到缓冲区中以发送到声卡。 t is the time. t是时间。 For some reason, the more the application calls this method, the slower it gets. 由于某种原因,应用程序调用此方法的次数越多,获取的速度越慢。 It just makes no sense, after 15 seconds it's slow enough to use a full core of my CPU and make the audio stutter. 只是没有意义,在15秒钟之后,它足够慢,无法使用我的CPU的完整内核并造成音频不稳定。

I'm 100% sure it's this piece of code, because if I replace it with a return 0, the time it takes to run it (measured with System.nanotime() ) is constant. 我100%确信这是这段代码,因为如果我将它替换为return 0,则运行它的时间(用System.nanotime()衡量)是恒定的。

Why is this happening? 为什么会这样呢? Is there something I can do to fix this? 有什么我可以解决的吗?

From the information here - while it is not clear how big your buffer is, you are incrementing t with each iteration. 根据此处的信息-虽然尚不清楚缓冲区的大小,但是每次迭代都会增加t。 Assuming your frequency is quite high, you are increasing the Sin() argument with each iteration. 假设频率很高,则每次迭代都增加Sin()参数。 Have checks to see if the argument is constantly increasing to a very high value. 进行检查以查看参数是否一直在增加到很高的值。 A quick and dirty test shows that Sin performance goes down - 快速而肮脏的测试表明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

Please give no points, so the solution would be given the answer of @mk: 请不要提出任何意见,因此将为解决方案提供@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;
}

i solved the problem with a lookup table: 我用查找表解决了这个问题:

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

it works... kinda, only problem is that i get a lot of distorsion on high frequencies. 它有效...有点,唯一的问题是我在高频上有很多失真。 is there some interpolation method you can suggest? 有什么建议的插值方法吗?

Current versions of the Java framework will attempt to mod-reduce the argument to Math.sin using a mathematically-perfect value of 2π, rather than the value Math.PI*2 . Java框架的当前版本将尝试使用数学上完美的2π值(而不是Math.PI*2来mod-reduce Math.sin参数。 For code such as yours, this means the code will take longer and yield less accurate results than if mod reduction had been performed using the same scale factor as was used in multiplication (ie Math.PI*2 ). 对于像您这样的代码,这意味着与使用乘法运算中相同的比例因子(即Math.PI*2 )执行mod缩减操作相比,代码将花费更长的时间,并且产生的准确度也会更低。 To get good accuracy and speed, you should perform modulo reduction before doing the multiplication, using something like: 为了获得良好的精度和速度,您应该在进行乘法之前执行模归约,方法如下:

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
}

That will ensure that neither sin nor cos is ever used with an argument greater than π/4, which is according to the documentation the point where Java switches to using slow and counterproductive range reduction. 这将确保不会使用大于π/ 4的参数来使用sincos ,根据文档,这是Java转向使用缓慢且适得其反的范围缩小的观点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM