【问题标题】:Does this mean that Java Math.floor is extremely slow?这是否意味着 Java Math.floor 非常慢?
【发布时间】:2012-08-16 11:18:29
【问题描述】:

我不太会 Java。

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

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

在代码中存在一个执行相关任务的 floor 函数。

  1. 有人知道解决此问题的典型方法吗?
  2. 我注意到我的 floor() 函数是用名为 StrictMath 的东西定义的。 Java有类似-ffast-math的东西吗?我期望必须有一种方法可以将 floor 函数更改为计算上更合理的值,而无需自己编写。

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

编辑

所以有几个人建议我尝试做演员表。我试过这个,walltime绝对没有变化。

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

413742 铸楼功能

394675 Math.floor

这些测试是在没有分析器的情况下运行的。尝试使用分析器,但运行时间发生了巨大变化(15 分钟以上,所以我退出了)。

【问题讨论】:

  • 如果您必须使用StrictMath.*(通常比Math.* 更准确)- 如果重复计算,您可能希望在地图中缓存地板结果楼层的值相同。
  • 你能提供更多关于你的代码的上下文吗? Math.floor() 有多少电话?你使用什么样的数字?哦,C++ 或 MATLAB 在数学运算方面的速度更快这一事实并不令人惊讶。
  • @Nishant OP 声明 Math.floor() 代表 StrictMath.floor(),请参阅:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
  • @posdef 嘿,感谢您指出这一点。我看到 StrictMath 使用原生 -- public static native double floor(double a);
  • 即使在 C/C++ 中,floor() 的速度也非常慢。这并不是说地板是一项缓慢的操作。有一些按位技巧可以使它非常快。但是有许多极端情况需要检查和处理。 SSE4.1 指令集添加了用于舍入操作的本机指令,包括floor()。但访问这些需要 C/C++ 和编译器内在函数或程序集。

标签: java performance profiling floor


【解决方案1】:

首先:您的分析器显示您将 99% 的 cpu 时间花在了 floor 函数上。这并不表示地板很慢。如果你除了 floor() 什么都不做,那是完全理智的。但是,由于其他语言似乎更有效地实现了地板,因此您的假设可能是正确的。

我从学校知道,可以通过转换为整数/长整数来实现 floor 的幼稚实现(仅适用于正数,仅适用于负数)。这与语言无关,并且是 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;
    }

}

【讨论】:

  • 好吧,我不能说它是快两倍还是快 11 倍。他将不得不自己分析它。注意不要用完问号! :)
  • 哈哈 :)... 我会记住这一点的。但是,你是基于不确定性来回答的,没有它,答案就变成了一个建议。
  • 你真的应该用可验证的研究来支持你的答案,否则它可能会被否决或被删除。
  • 已编辑以使答案更完整。
【解决方案2】:

您可能想试试FastMath

这是关于performance of Math in Java vs. Javascript 的帖子。关于为什么默认的数学库很慢,有一些很好的提示。他们正在讨论floor 以外的其他操作,但我想他们的发现可以概括。我觉得很有趣。

编辑

根据this bug entry, floor 已在 7(b79)、6u21(b01) 中实现了纯 Java 代码,从而获得了更好的性能。 JDK 6 中的 floor 代码仍然比 FastMath 中的代码长一点,但可能不会造成这种性能。降解。你用的是什么JDK?你能试试更新的版本吗?

【讨论】:

  • 我正在使用我正在使用 Sun/Oracle JDK 1.7.05,我在 i3 Gentoo 系统上运行。
【解决方案3】:

Math.floor() 在我的机器上非常快,每次调用大约 7 纳秒,在一个紧密的循环中。 (Windows 7、Eclipse、Oracle JDK 7)。我希望它在几乎所有情况下都非常快,如果它成为瓶颈,我会感到非常惊讶。

一些想法:

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

【讨论】:

【解决方案4】:

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

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

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

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


如果假设不成立,以下是分析结果的两种替代解释。

  1. 这是测量异常;即分析器不知何故弄错了。尝试使用不同的分析器。

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

【讨论】:

  • 检查 floor() 调用的调用次数也是一个很好的措施,可以查看对该方法的调用是否比预期的多。
  • 问题是通过使用强制转换解决的,但最初的问题涉及分析分析器结果。有趣的是,具有所有优化功能的 C++ 版本的运行速度提高了约 13 倍。最后,当使用强制转换时,ArrayList.add 函数按预期占用了 66% 的时间。
【解决方案5】:

值得注意的是,监视方法需要一些开销,对于 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

【讨论】:

  • 感谢您的建议。我尝试做这样的事情,但没有帮助。
  • @misha 正如我所提到的,visual vm 可以抛出一些非常短的方法,这些方法通常被称为比实际更重要的方法。即使在商业分析器中,您也需要小心获得的结果。如果没有分析器,请尝试调用 Math.floor 两次以查看它的速度有多慢,这将使您了解它增加了多少时间。
  • 这是有趣的分析信息。我会再看看我的仪器。我的跑步是在没有分析器的情况下完成的。
  • @misha 你不能在不改变行为的情况下移除地板,但你可以调用它两到三遍,看看它有多慢;)
【解决方案6】:

如果您的算法非常依赖于 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);
}

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

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

【讨论】:

    猜你喜欢
    • 2020-05-31
    • 1970-01-01
    • 1970-01-01
    • 2014-08-15
    • 1970-01-01
    • 1970-01-01
    • 2014-12-07
    • 2020-08-09
    • 1970-01-01
    相关资源
    最近更新 更多