16

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

对于 C,sincos在通用 unixm数学库中有该函数。实际上,至少在 i386 上,这应该是一条汇编指令,fsincos.

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

我猜这些好处是存在的,因为在计算正弦和余弦方面存在明显的重叠:sin(x)^2 + cos(x)^2 = 1. 但是 AFAIK 尝试将其简化为 并没有回报cos = Math.sqrt(1 - sin*sin),因为该sqrt功能的成本相似。

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

或者 Hotspot 编译器是否足够聪明,可以识别出我需要两者,并将其编译为sincos命令?我可以测试它是否识别它,并且我可以帮助它识别它吗,例如通过确保Math.sinandMath.cos命令在我的代码中是直接连续的?从 Java 语言的角度来看,这实际上是最有意义的:让编译器优化它以使用fsincos汇编调用。

从一些汇编器文档中收集:

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应该需要一个额外的pop,但这应该是1个时钟周期。假设 CPU 也没有对此进行优化,sincos应该几乎是调用sin两次的两倍(第二次计算余弦;所以我认为它需要做一个加法)。sqrt在某些情况下可能更快,但正弦可以更快。

更新:我在 C 中做了一些实验,但没有定论。有趣的是,sincos它似乎比sin(没有)还要快一些,并且当您计算两者时,cosGCC 编译器将使用- 所以它可以做我希望 Hotspot 做的事情(或者 Hotspot 也做?)。我还不能阻止编译器通过使用除了不使用. 然后它将回退到 C ,而不是.fsincossincosfsincoscossinfsin

4

5 回答 5

11

我已经用卡尺进行了一些微基准测试。在 -4*pi .. 4*pi 范围内的(预先计算的)随机数数组上进行 10000000 次迭代。我尽我最大的努力来获得我能想到的最快的 JNI 解决方案 - 很难预测你是否真的会得到fsincos或一些被模仿sincos的 . 报告的数字是 10 次卡尺试验中最好的(这又包括 3-10 次试验,报告了其中的平均值)。所以大致每个内循环运行 30-100 次。

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

  • Math.sin仅(参考)
  • Math.cos仅(参考)
  • Math.sin+Math.cos
  • sincos通过 JNI
  • Math.sin+ cos via Math.sqrt( (1+sin) * (1-sin) )+ 符号重建
  • Math.cos+ sin via Math.sqrt( (1+cos) * (1-cos) )+ 符号重构

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

符号重建通过x %= TWOPI; if (x<0) x+=TWOPI;然后检查象限。如果您知道如何用更少的 CPU 做到这一点,我很乐意听到。

数字损失sqrt似乎是可以的,至少对于常见的角度。在粗略实验的 1e-10 范围内。

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

sqrt(1-s*s)vs.sqrt((1+s)*(1-s))产生大约 0,01的差异。如您所见,sqrt基于方法的方法胜过其他任何方法(因为我们目前无法sincos在纯 Java 中访问)。JNIsincos比计算sin和更好cos,但该sqrt方法仍然更快。cos本身似乎始终比 好一个刻度 (0,01) sin,但重构符号的大小写区分有一个额外的>测试。我不认为我的结果支持sin+sqrtcos+sqrt显然更可取,但与那时相比,它们确实节省了大约 40% 的sin时间cos

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

如果我再次运行它,我还会添加 JNIsin和 anoop来估算 JNI 的成本。也许还可以sqrt通过 JNI 对技巧进行基准测试。只是为了确保sincos从长远来看我们确实需要一个内在函数。

于 2013-01-05T19:42:31.440 回答
1

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

于 2012-11-19T19:31:36.527 回答
1

您可以随时配置文件。

然而,一般来说,sqrt 的速度应该与除法相同,因为 div 和 sqrt 的内部实现非常相似。

正弦和余弦,OTOH 是用高达 10 度的多项式计算的,没有任何公共系数,并且可能难以模 2pi 减少——这是正余弦中唯一共享的公共部分(不使用 CORDIC 时)。

编辑修订的分析(纠正错字)显示时间差异

sin+cos:  1.580 1.580 1.840 (time for 200M iterations, 3 successive trials)
sincos:   1.080 0.900 0.920
sin+sqrt: 0.870 1.010 0.860
于 2012-11-19T19:31:59.853 回答
1

查看 Hotspot 代码,我相当确信 Oracle Hotspot VM 没有将 sin(a) + cos(a) 优化为 fsincos:请参见assembler_x86.cpp,第 7482ff 行。

但是,我怀疑单独使用 fsin 和 fcos 所增加的机器周期数很容易被其他操作(例如运行 GC)所掩盖。我会使用标准的 Java 功能并分析应用程序。只有当配置文件运行表明在 sin/cos 调用中花费了大量时间时,我才会冒险去做一些事情。

在这种情况下,我将创建一个使用 2 元素 jdoublearray 作为输出参数的 JNI 包装器。如果您只有一个使用 sincos JNI 操作的线程,则可以在 Java 代码中使用静态初始化的 double[2] 数组,该数组将被反复重用。

于 2012-11-20T11:12:40.417 回答
1

常规 Java中没有可用的fsincos 。此外,JNI 版本可能比对 java.lang.Math.sin() 和 cos() 的双重调用要慢。

我猜你担心 sin(x)/cos(x) 的速度。所以我给你一个快速三角运算的建议,代替 fsincos:查找表。下面是我的原帖。我希望它对你有帮助。

=====

我尝试使用查找表 (LUT) 在三角函数(sin 和 cos)上实现最佳性能。

我发现了什么:

  • LUT 可以比 java.lang.Math.sin()/cos() 快20-25 倍。可能与本机 fsin / fcos 一样快。也许和 fsincos 一样快。
  • 但是如果使用 0 到 45 度之间的角度,java.lang.Math.sin() 和 cos()比任何其他计算 sin/cos 的方法都要快;
  • 但请注意,低于 12 度的角度的 sin(x) 几乎 == x。它甚至更快;

  • 一些实现使用浮点数组来存储 sin,另一种用于存储 cos。这是不必要的。请记住:

cos(x) == sin(x + PI/2)

  • 也就是说,如果你有 sin(x) 表,你就有 cos(x) 表是免费的。

我使用 java.lang.Math.sin();对范围 [0..45] 内的角度进行了一些测试,使用 sin();一个简单的 360 位置查找表,一个优化的 LUT90,表值范围为 [0..90],但扩展为使用 [0..360];并使用插值查找表。注意警告后,java.lang.Math.sin() 比其他更快:

Size test: 10000000
Angles range: [0.0...45.0]
Time in ms
Trial | Math.sin() | Lut sin() | LUT90.sin() | Lut sin2() [interpolation]
0    312,5879        25,2280        27,7313      36,4127
1    12,9468         19,5467        21,9396      34,2344
2    7,6811          16,7897        18,9646      32,5473
3    7,7565          16,7022        19,2343      32,8700
4    7,6634          16,9498        19,6307      32,8087

此处提供的资源GitHub

但是,如果您需要range[-360..360]的高性能,java.lang.Math 库会更慢。查找表 (LUT) 大约快 20 倍。如果需要高精度,可以使用带插值的 LUT,它比 java.lang.Math 慢一点,但仍然快。在上面的链接中查看我在 Math2.java 中的 sin2()。

以下数字用于角度高范围:

Size test: 10000000
Angles range: [-360.0...360.0]
Time in ms
Trial|Math.sin() | Lut sin() | LUT90.sin() | Lut.sin2() [interpolation]
0    942,7756        35,1488        47,4198      42,9466
1    915,3628        28,9924        37,9051      41,5299
2    430,3372        24,8788        34,9149      39,3297
3    428,3750        24,8316        34,5718      39,5187
于 2016-02-15T02:37:09.073 回答