10

我需要Math.exp()经常从 java 计算,是否有可能让本机版本比javaMath.exp()运行得更快?

我只尝试了 jni + C,但它比普通的java慢。

4

15 回答 15

15

这已被多次请求(参见例如此处)。这是从这篇博文复制而来的 Math.exp() 的近似值:

public static double exp(double val) {
    final long tmp = (long) (1512775 * val + (1072693248 - 60801));
    return Double.longBitsToDouble(tmp << 32);
}

它与具有 2048 个条目和条目之间的线性插值的查找表基本相同,但所有这些都使用 IEEE 浮点技巧。它比我机器上的 Math.exp() 快 5 倍,但如果使用 -server 编译,这可能会有很大差异。

于 2009-01-08T16:43:31.520 回答
12

+1 编写自己的 exp() 实现。也就是说,如果这确实是您的应用程序中的瓶颈。如果您可以处理一些不准确的问题,那么有许多非常有效的指数估计算法,其中一些可以追溯到几个世纪以前。据我了解,Java 的 exp() 实现相当慢,即使对于必须返回“精确”结果的算法也是如此。

哦,不要害怕用纯 Java 编写 exp() 实现。JNI 有很多开销,而且 JVM 能够在运行时优化字节码,有时甚至超出了 C/C++ 能够实现的程度。

于 2008-09-15T20:22:54.770 回答
6

使用 Java 的。

此外,缓存 exp 的结果,然后您可以比再次计算更快地查找答案。

于 2008-09-15T20:07:01.877 回答
5

您还想Math.exp()在 C 中包装任何循环调用。否则,Java 和 C 之间的编组开销将压倒任何性能优势。

于 2008-09-15T20:08:58.877 回答
3

如果您分批执行它们,您也许可以让它运行得更快。进行 JNI 调用会增加开销,因此您不想为需要计算的每个 exp() 都这样做。我会尝试传递一个包含 100 个值的数组并获取结果以查看它是否有助于提高性能。

于 2008-09-15T20:17:36.310 回答
2

真正的问题是,这是否已成为您的瓶颈?您是否分析过您的应用程序并发现这是导致速度变慢的主要原因?

如果没有,我建议使用 Java 的版本。尽量不要预先优化,因为这只会导致开发速度变慢。您可能会在一个可能不是问题的问题上花费大量时间。

话虽这么说,我认为你的测试给了你答案。如果 jni + C 速度较慢,请使用 java 的版本。

于 2008-09-15T20:11:44.483 回答
2

Commons Math3附带一个优化版本:FastMath.exp(double x). 它确实显着加快了我的代码速度。

Fabien进行了一些测试,发现它的速度几乎是以下速度的两倍Math.exp()

 0.75s for Math.exp     sum=1.7182816693332244E7
 0.40s for FastMath.exp sum=1.7182816693332244E7

这是javadoc:

计算 exp(x),函数结果接近四舍五入。对于 99.9% 的输入值,它将正确四舍五入为理论值,否则会出现 1 个 UPL 错误。

方法:

    Lookup intVal = exp(int(x))
    Lookup fracVal = exp(int(x-int(x) / 1024.0) * 1024.0 );
    Compute z as the exponential of the remaining bits by a polynomial minus one
    exp(x) = intVal * fracVal * (1 + z)

准确度:计算以 63 位精度完成,因此结果应正确舍入 99.9% 的输入值,否则 ULP 误差小于 1。

于 2014-05-30T21:21:15.473 回答
0

由于 Java 代码将使用即时 (JIT) 编译器编译为本机代码,因此没有理由使用 JNI 调用本机代码。

此外,您不应缓存输入参数为浮点实数的方法的结果。在时间上获得的收益将在使用的空间量上损失很多。

于 2008-09-15T20:09:52.400 回答
0

使用 JNI 的问题是调用 JNI 所涉及的开销。Java 虚拟机现在已经非常优化,对内置 Math.exp() 的调用会自动优化为直接调用 C exp() 函数,甚至可以优化为直接的 x87 浮点程序集指示。

于 2008-09-15T20:10:27.607 回答
0

使用 JNI 会产生一些开销,另请参阅:http: //java.sun.com/docs/books/performance/1st_edition/html/JPNativeCode.fm.html

因此,正如其他人建议的那样,尝试整理涉及使用 JNI 的操作。

于 2008-09-15T20:13:27.880 回答
0

编写您自己的,根据您的需求量身定制。

例如,如果所有指数都是 2 的幂,则可以使用位移。如果您使用有限范围或一组值,则可以使用查找表。如果您不需要精确的精度,您可以使用不精确但速度更快的算法。

于 2008-09-15T20:18:24.360 回答
0

跨 JNI 边界调用会产生成本。

如果您也可以将调用 exp() 的循环移动到本机代码中,以便只有一个本机调用,那么您可能会得到更好的结果,但我怀疑它会比纯 Java 解决方案快得多。

我不知道您的应用程序的详细信息,但如果您有一组相当有限的调用可能参数,您可以使用预先计算的查找表来使您的 Java 代码更快。

于 2008-09-15T20:20:06.197 回答
0

exp 有更快的算法,具体取决于您要完成的工作。问题空间是否限制在一定范围内,是否只需要一定的分辨率、精度或准确度等。

如果你很好地定义了你的问题,你可能会发现你可以使用一个带有插值的表格,例如,这将使几乎任何其他算法都被淘汰。

您可以对 exp 应用哪些约束来获得性能权衡?

-亚当

于 2008-09-15T20:21:14.663 回答
0

我运行了一个拟合算法,拟合结果的最小误差远大于 Math.exp() 的精度。

超越函数总是比加法或乘法慢得多,这是众所周知的瓶颈。如果您知道您的值在一个狭窄的范围内,您可以简单地构建一个查找表(两个排序数组;一个输入,一个输出)。使用 Arrays.binarySearch 查找正确的索引并使用 [index] 和 [index+1] 处的元素插入值。

另一种方法是拆分号码。让我们取例如 3.81 并将其拆分为 3+0.81。现在将 e = 2.718 乘以 3 次,得到 20.08。

现在到 0.81。0 到 1 之间的所有值都与众所周知的指数级数快速收敛

1+x+x^2/2+x^3/6+x^4/24....等

尽可能多地使用精确度;不幸的是,如果 x 接近 1,它会变慢。假设你去 x^4,那么你得到 2.2445 而不是正确的 2.2448

然后将结果 2.781^3 = 20.08 与 2.781^0.81 = 2.2445 相乘,得到结果 45.07,误差为两千分之一(正确:45.15)。

于 2008-09-15T21:45:07.320 回答
0

它可能不再相关,但您知道,在最新版本的 OpenJDK(请参见此处)中,应该将 Math.exp 设为内部(如果您不知道那是什么,请查看此处)。

这将使大多数架构上的性能无与伦比,因为这意味着 Hotspot VM 将在运行时用特定于处理器的 exp 实现替换对 Math.exp 的调用。您永远无法击败这些调用,因为它们针对架构进行了优化......

于 2013-03-01T10:44:39.730 回答