【问题标题】:faster Math.exp() via JNI?通过 JNI 更快的 Math.exp()?
【发布时间】:2010-09-09 03:30:58
【问题描述】:

我需要非常频繁地从java计算Math.exp(),是否有可能让原生版本比javaMath.exp()运行得更快??

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

【问题讨论】:

  • 您是否进行过任何性能测试以获取 Math.exp() 与 JNI 版本所用时间的准确数字?调用 10k 次后看看 JIT 的效果怎么样?
  • 这取决于你的JVM,但通常Math.exp是用C实现的。不过,您可能希望使用更快(不太精确)的算法。

标签: java c optimization java-native-interface


【解决方案1】:

使用 Java 的。

另外,缓存 exp 的结果,然后你可以比重新计算更快地查找答案。

【讨论】:

  • 如何缓存结果?缓存可能非常昂贵:我尝试使用 HashMap,它比简单地计算 exp 慢两倍。在我的测试中,我计算了 71M exp,但“仅”使用了 180 万个不同的参数。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              根据自己的需要编写自己的代码。

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

              【讨论】:

                【解决方案9】:

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

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

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

                【讨论】:

                  【解决方案10】:

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

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

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

                  -亚当

                  【讨论】:

                    【解决方案11】:

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

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

                    【讨论】:

                    • 这里有两个重点:(1) JNI 开销通常超过所有其他考虑; (2) JVM JIT 在优化小型方法方面出奇的好(有时等于或快于 C/C++),只要机器足够“预热”。
                    【解决方案12】:

                    我运行了一个拟合算法,拟合结果的最小误差要大得多 比 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)。

                    【讨论】:

                      【解决方案13】:

                      这已被多次请求(参见例如here)。这是从 this blog posting 复制的 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 编译,这可能会有很大差异。

                      【解决方案14】:

                      它可能不再相关,但你知道,在 OpenJDK 的最新版本中(参见 here),Math.exp 应该是一个内在的(如果你不知道那是什么,检查here)。

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

                      【讨论】:

                        【解决方案15】:

                        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。

                        【讨论】:

                        • 最适合我的解决方案!
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2016-08-04
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多