在Java中使用“sincos”

2022-09-03 07:08:57

在很多情况下,我不仅需要正弦,还需要相同参数的余弦。

对于C,在常见的unix数学库中有一个函数。实际上,至少在i386上,这应该是一个单一的汇编指令。sincosmfsincos

sincos, sincosf, sincosl - 同时计算 sin 和 cos

我想这些好处之所以存在,是因为计算正弦和余弦有明显的重叠:。但是 AFAIK 尝试将其快捷方式作为 是没有回报的,因为该函数的成本相似。sin(x)^2 + cos(x)^2 = 1cos = Math.sqrt(1 - sin*sin)sqrt

有没有办法在Java中获得同样的好处?我想我会为此付出代价;这可能会使所有的努力变得毫无意义,因为增加了垃圾收集。double[]

或者 Hotspot 编译器是否足够聪明,能够认识到我需要两者,并将它编译为命令?我是否可以测试它是否识别它,我能否帮助它识别这一点,例如,通过确保和命令在我的代码中直接连续?从Java语言的角度来看,这实际上是最有意义的:让comiler优化它以使用汇编调用。sincosMath.sinMath.cosfsincos

从一些汇编程序文档中收集:

Variations    8087         287        387      486     Pentium
fsin           -            -       122-771  257-354   16-126  NP
fsincos        -            -       194-809  292-365   17-137  NP
 Additional cycles required if operand > pi/4 (~3.141/4 = ~.785)
sqrt        180-186      180-186    122-129   83-87    70      NP

fsincos应该需要一个额外的流行音乐,但这应该在1个时钟周期。假设CPU也没有优化这一点,应该几乎是调用两次的两倍(第二次计算余弦;所以我认为它需要做一个加法)。 在某些情况下可能会更快,但正弦可以更快。sincossinsqrt

更新:我在C中做了一些实验,但它们没有定论。有趣的是,它似乎比(没有)稍微快一点,并且GCC编译器将在您计算两者时使用- 所以它做了我希望Hotspot做的事情(或者Hotspot也是如此?)。我还无法阻止编译器通过使用来战胜我,除非不使用.然后它将回退到 C ,而不是 。sincossincosfsincossincosfsincoscossinfsin


答案 1

我用卡尺做了一些微型桥标记。在 -4*pi 范围内的(预先计算的)随机数数组上进行 1000000 次迭代。4*圆周率。我尽力获得我能想到的最快的JNI解决方案 - 很难预测你是否真的会得到或一些模仿。报告的数字是10项卡钳试验中最好的(这些试验又包括3-10项试验,报告了平均值)。所以大约是30-100次内部循环的运行。fsincossincos

我已经对几个变体进行了基准测试:

  • Math.sin仅(参考)
  • Math.cos仅(参考)
  • Math.sin + Math.cos
  • sincos通过 JNI
  • Math.sin+ cos via + sign reconstructionMath.sqrt( (1+sin) * (1-sin) )
  • Math.cos+ sin via + sign reconstructionMath.sqrt( (1+cos) * (1-cos) )

(1+sin)*(1-sin)=1-sin*sin在数学上,但如果罪接近1,它应该更精确吗?运行时差异很小,您节省了一个添加。

通过签名重建,然后检查象限。如果你有一个想法,如何用更少的CPU做到这一点,我会很高兴听到。x %= TWOPI; if (x<0) x+=TWOPI;

数值损失似乎是可以的,至少对于普通角度是这样。在1e-10的范围内从粗略的实验。sqrt

Sin         1,30 ==============
Cos         1,29 ==============
Sin, Cos    2,52 ============================
JNI sincos  1,77 ===================
SinSqrt     1,49 ================
CosSqrt     1,51 ================

与 产生大约0,01的差异。正如你所看到的,基于的方法战胜了其他任何方法(因为我们目前无法在纯Java中访问)。JNI 比 计算 和 更好,但这种方法仍然更快。 本身似乎总是比 (0,01) 好一个刻度 ,但是重构符号的情况区分有一个额外的测试。我不认为我的结果支持这两者或显然是可取的,但与当时相比,它们确实节省了大约40%的时间。sqrt(1-s*s)sqrt((1+s)*(1-s))sqrtsincossincossincossqrtcossin>sin+sqrtcos+sqrtsincos

如果我们将Java扩展为具有内在优化的sincos,那么这可能会更好。恕我直言,这是一个常见的用例,例如在图形中。当用于AWT,Batik等时,许多应用都可以从中受益。

如果我再次运行它,我还会添加JNI和a来估计JNI的成本。也许还可以通过JNI对技巧进行基准测试。只是为了确保从长远来看,我们确实想要一个内在的东西。sinnoopsqrtsincos


答案 2

大多数 sin 和 cos 计算都是直接调用硬件。没有比这更快的计算方法了。具体来说,在+- pi / 4范围内,速率非常快。如果通常使用硬件加速,并尝试将值限制为指定的值,则应该没问题。来源


推荐