通过 JNI 更快的 Math.exp() ?
-
09-06-2019 - |
题
我需要计算 Math.exp()
非常频繁地从 java 中获取,是否有可能让本机版本运行得比 爪哇的 Math.exp()
??
我只尝试了 jni + C,但它比普通的慢 爪哇.
解决方案
+1 编写您自己的 exp() 实现。也就是说,如果这是 真的 您的应用程序中的瓶颈。如果您可以处理一点不准确性,那么有许多非常有效的指数估计算法,其中一些可以追溯到几个世纪前。据我了解,Java 的 exp() 实现相当慢,即使对于必须返回“精确”结果的算法也是如此。
哦,不要害怕用纯 Java 编写 exp() 实现。JNI 的开销很大,而且 JVM 能够在运行时优化字节码,有时甚至超出了 C/C++ 能够实现的程度。
其他提示
这已经被多次请求(参见例如 这里)。这是 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 进行编译,这可能会有很大差异。
使用Java的。
另外,缓存 exp 的结果,然后您可以比再次计算它们更快地查找答案。
你想要包装任何循环的调用 Math.exp()
在C语言中也是如此。否则,Java 和 C 之间的编组开销将压倒任何性能优势。
如果您分批进行,您可能可以让它运行得更快。进行 JNI 调用会增加开销,因此您不想为每个需要计算的 exp() 执行此操作。我会尝试传递一个包含 100 个值的数组并获取结果,看看它是否有助于性能。
真正的问题是,这是否成为你的瓶颈?您是否对您的应用程序进行了分析并发现这是导致速度变慢的主要原因?
如果没有,我建议使用 Java 版本。尽量不要预先优化,因为这只会导致开发速度减慢。您可能会在一个可能不是问题的问题上花费大量时间。
话虽这么说,我认为你的测试已经给了你答案。如果jni + C较慢,请使用java的版本。
公共数学3 附带优化版本: FastMath.exp(double x)
. 。它确实显着加快了我的代码速度。
法比安 进行了一些测试,发现它的速度几乎是两倍 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。
由于 Java 代码将使用即时 (JIT) 编译器编译为本机代码,因此实际上没有理由使用 JNI 来调用本机代码。
此外,您不应缓存输入参数为浮点实数的方法的结果。时间上获得的收益将在所使用的空间量上损失惨重。
使用 JNI 的问题是调用 JNI 所涉及的开销。如今,Java 虚拟机已经得到了相当程度的优化,对内置 Math.exp() 的调用会自动优化为直接调用 C exp() 函数,甚至可能会优化为直接的 x87 浮点汇编指示。
使用 JNI 会产生一定的开销,另请参阅:http://java.sun.com/docs/books/performance/1st_edition/html/JPNativeCode.fm.html
因此,正如其他人建议的那样,尝试整理涉及使用 JNI 的操作。
根据您的需求编写您自己的内容。
例如,如果所有指数都是 2 的幂,则可以使用位移位。如果您使用有限范围或一组值,则可以使用查找表。如果您不需要精确的精度,则可以使用不精确但速度更快的算法。
跨 JNI 边界调用会产生相关成本。
如果您也可以将调用 exp() 的循环移至本机代码中,以便只有一个本机调用,那么您可能会得到更好的结果,但我怀疑它会比纯 Java 解决方案快得多。
我不知道您的应用程序的详细信息,但如果您的调用的可能参数集相当有限,您可以使用预先计算的查找表来使您的 Java 代码更快。
根据您想要完成的任务,有更快的 exp 算法。问题空间是否被限制在一定的范围内,是否只需要一定的分辨率、精度、或者准确度等?
如果你很好地定义了你的问题,你可能会发现你可以使用带有插值的表格,例如,这将使几乎任何其他算法都出局。
您可以对 exp 应用哪些限制来获得性能权衡?
-亚当
我运行拟合算法,拟合结果的最小误差大于Math.exp()的精度。
超越函数总是比加法或乘法慢得多,也是众所周知的瓶颈。如果您知道您的值在一个狭窄的范围内,您可以简单地构建一个查找表(两个排序数组;一个输入,一个输出)。使用 Arrays.binarySearch 查找正确的索引并使用 [index] 和 [index+1] 处的元素插入值。
另一种方法是拆分号码。让我们以3.81 并将其除以 3+0.81。现在将 e = 2.718 乘以三倍,得到 20.08。
现在到 0.81。0 到 1 之间的所有值都与众所周知的指数级数快速收敛
1+x+x^2/2+x^3/6+x^4/24....ETC。
为了精确起见,采用尽可能多的术语;不幸的是,如果 x 接近 1,速度会变慢。假设你转到 x^4,那么你得到 2.2445 而不是正确的 2.2448
然后将结果乘以2.781^3 = 20.08与2.781^0.81 = 2.2445,并且结果45.07,错误为两千个(正确::45.15)。