这是否意味着Java Math.floor非常慢?

时间:2012-08-21 06:18:41

标签: java performance profiling floor

我的Java不多。

我正在编写一些优化的数学代码,我对我的分析器结果感到震惊。我的代码收集值,交错数据,然后根据它选择值。 Java运行速度比我的C ++和MATLAB实现慢。

我正在使用javac 1.7.0_05 我使用的是Sun / Oracle JDK 1.7.05

存在一个在代码中执行相关任务的floor函数。 java math.floor profile results

  1. 有人知道解决此问题的范式方法吗?
  2. 我注意到我的floor()函数定义为StrictMath。 Java有-ffast-math之类的东西吗?我期待必须有一种方法可以将地板功能更改为更合理的,而无需编写自己的功能。

    public static double floor(double a) {
        return StrictMath.floor(a); // default impl. delegates to StrictMath
    }
    
  3. 修改

    所以有些人建议我尝试演员。我尝试了这个,而且在时间上没有任何变化。

    private static int flur(float dF)
    {
        return (int) dF;
    }
    

    413742演员职能

    394675 Math.floor

    这些测试是在没有分析器的情况下运行的。我们努力使用分析器,但运行时间发生了翻天覆地的变化(15分钟以上,因此我退出了)。

6 个答案:

答案 0 :(得分:8)

您可能想尝试FastMath

这是关于performance of Math in Java vs. Javascript的帖子。关于默认数学库缓慢的原因,有一些很好的提示。他们正在讨论除floor以外的其他行动,但我猜他们的发现可以推广。我发现它很有趣。

修改

根据this bug entry,floor已在7(b79),6u21(b01)中实现了纯java代码,从而提高了性能。 JDK 6中的楼层代码仍然比FastMath中的楼层代码长一些,但可能不会对此类影响负责。降解。您使用的JDK是什么?你能试试更新的版本吗?

答案 1 :(得分:6)

这是对您的假设进行的一种健全性检查,即代码确实在floor中花费了99%的时间。假设您拥有算法的Java和C ++版本,这些版本在它们产生的输出方面都是正确的。为了论证,让我们假设这两个版本将等效的floor函数调用相同的次数。所以时间函数是

t(input) = nosFloorCalls(input) * floorTime + otherTime(input)

其中floorTime是在平台上调用floor所需的时间。

现在,如果您的假设是正确的,并且floorTime在Java上的成本要高得多(在大约99%的执行时间内),那么您可能希望应用程序的Java版本运行比C ++版本慢的大因子(50倍或更多)。如果你没有看到这一点,那么你的假设很可能是假的。


如果假设为假,则以下是对分析结果的两种替代解释。

  1. 这是测量异常;即剖析器以某种方式弄错了。尝试使用其他分析器。

  2. 您的代码的Java版本中存在一个错误,导致它比代码的C ++版本多次调用floor

答案 2 :(得分:5)

Math.floor()在我的机器上非常快,每次通话大约7纳秒。 (Windows 7,Eclipse,Oracle JDK 7)。我希望它在几乎所有情况下都会非常快,如果它成为瓶颈,我会非常惊讶。

一些想法:

  • 我建议重新运行一些基准,而不运行探查器。有时候,分析器在检测二进制文件时会产生虚假的开销 - 特别是对于可能内联的Math.floor()这样的小函数。
  • 尝试几种不同的JVM,你可能会遇到一个模糊的错误
  • 在优秀的Apache Commons Math库中尝试FastMath类,其中包含一个新的floor实现。如果它更快,我会真的很惊讶,但你永远不会知道。
  • 检查您是否正在运行任何可能干扰Java调用本机代码的能力的虚拟化技术或类似技术(在java.lang.Math函数中使用的Math.floor()函数包括{{1}})

答案 3 :(得分:4)

首先:您的探查器显示您在地板功能中花费了99%的cpu时间。这并不表示地板很慢。如果你什么都不做,但地板()完全是理智的。然而,由于其他语言似乎更有效地实现了楼层,因此您的假设可能是正确的。

我从学校知道,一个天真的地板实现(仅适用于正数,一个负数适用于负数)可以通过转换为整数/长来完成。这与语言无关,也是CS课程的一些常识。

这是一些微型长凳。在我的机器上工作并支持我在学校学到的东西;)

rataman@RWW009 ~/Desktop
$ javac Cast.java && java Cast
10000000 Rounds of Casts took 16 ms

rataman@RWW009 ~/Desktop
$ javac Floor.java && java Floor
10000000 Rounds of Floor took 140 ms
public class Cast/Floor {

    private static final int ROUNDS = 10000000;

    public static void main(String[] args)
    {
        double[] vals = new double[ROUNDS];
        double[] res = new double[ROUNDS];

        // awesome testdata
        for(int i = 0; i < ROUNDS; i++)
        {
            vals[i] = Math.random() * 10.0;
        }

        // warmup
        for(int i = 0; i < ROUNDS; i++)
        {
            res[i] = floor(vals[i]);
        }

        long start = System.currentTimeMillis();
        for(int i = 0; i < ROUNDS; i++)
        {
            res[i] = floor(vals[i]);
        }
        System.out.println(ROUNDS + " Rounds of Casts took " + (System.currentTimeMillis() - start) +" ms");
    }

    private static double floor(double arg)
    {
        // Floor.java
        return Math.floor(arg);
        // or Cast.java
        return (int)arg;
    }

}

答案 4 :(得分:4)

值得注意的是,监视方法需要一些开销,而在VisualVM的情况下,这是相当高的。如果你有一个经常被调用的方法,但很少有它可以看起来使用大量的CPU。例如我曾经看过Integer.hashCode()作为一名大打击手。 ;)

在我的机器上,一个地板需要5.6 ns,但是需要2.3 ns。你可能想在你的机器上试试这个。


除非您需要处理角落情况,否则普通投射会更快。

// Rounds to zero, instead of Negative infinity.
public static double floor(double a) {
    return (long) a;
}

public static void main(String... args) {
    int size = 100000;
    double[] a = new double[size];
    double[] b = new double[size];
    double[] c = new double[size];
    for (int i = 0; i < a.length; i++) a[i] = Math.random()  * 1e6;

    for (int i = 0; i < 5; i++) {
        timeCast(a, b);
        timeFloor(a, c);
        for (int j = 0; j < size; j++)
            if (b[i] != c[i])
                System.err.println(a[i] + ": " + b[i] + " " + c[i]);
    }
}

public static double floor(double a) {
    return a < 0 ? -(long) -a : (long) a;
}

private static void timeCast(double[] from, double[] to) {
    long start = System.nanoTime();
    for (int i = 0; i < from.length; i++)
        to[i] = floor(from[i]);
    long time = System.nanoTime() - start;
    System.out.printf("Cast took an average of %.1f ns%n", (double) time / from.length);
}

private static void timeFloor(double[] from, double[] to) {
    long start = System.nanoTime();
    for (int i = 0; i < from.length; i++)
        to[i] = Math.floor(from[i]);
    long time = System.nanoTime() - start;
    System.out.printf("Math.floor took an average of %.1f ns%n", (double) time / from.length);
}

打印

Cast took an average of 62.1 ns
Math.floor took an average of 123.6 ns
Cast took an average of 61.9 ns
Math.floor took an average of 6.3 ns
Cast took an average of 47.2 ns
Math.floor took an average of 6.5 ns
Cast took an average of 2.3 ns
Math.floor took an average of 5.6 ns
Cast took an average of 2.3 ns
Math.floor took an average of 5.6 ns

答案 5 :(得分:0)

如果您的算法非常依赖于 Math.floor(和 Math.ceil),它可能会成为一个令人惊讶的瓶颈。 这是因为这些函数处理您可能不关心的边缘情况(例如负零和正零等)。只需查看这些函数的实现即可了解它们实际在做什么;那里有惊人数量的分支。

还要考虑一下 Math.floor/ceil 只接受一个双精度值作为参数并返回一个双精度值,这可能是您不想要的。如果你只是想要一个 int 或 long,那么 Math.floor 中的一些检查就完全没有必要了。

有些人建议简单地转换为 int,只要您的值是正数,它就会起作用(并且您的算法不依赖于 Math.floor 检查的边缘情况)。如果是这种情况,简单的强制转换是最快的解决方案(根据我的经验)。

例如,如果您的值可能是负数,而您想要浮点数中的 int,则可以执行以下操作:

public static final int floor(final float value) {
    return ((int) value) - (Float.floatToRawIntBits(value) >>> 31);
}

(它只是从转换中减去浮点数的符号位以使其对负数正确,同时防止出现“if”)

根据我的经验,这比 Math.floor 快很多。如果不是,我建议检查您的算法,或者您可能遇到了 JVM 性能错误(这种可能性要小得多)。