19

我不太懂Java。

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

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

在代码中存在一个执行相关任务的 floor 函数。 java math.floor 配置文件结果

  1. 有人知道解决此问题的典型方法吗?
  2. 我注意到我的floor()函数是用名为StrictMath. -ffast-mathJava有类似的东西吗?我期望必须有一种方法可以将 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 数学地板

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

4

6 回答 6

8

您可能想尝试一下FastMath

这是一篇关于Java 与 Javascript 中的数学性能的文章。关于为什么默认的数学库很慢,有一些很好的提示。他们正在讨论除 之外的其他操作floor,但我想他们的发现可以概括。我觉得很有趣。

编辑

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

于 2012-08-21T06:34:23.720 回答
6

这是对您的假设的健全性检查,即代码实际上将 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++ 版本的代码多很多。

于 2012-08-21T07:14:09.890 回答
5

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

一些想法:

  • 我建议在没有运行探查器的情况下重新运行一些基准测试。有时,分析器在检测二进制文件时会产生虚假的开销——尤其是对于Math.floor()可能被内联的小函数。
  • 尝试几个不同的 JVM,你可能遇到了一个不起眼的错误
  • 试试FastMath优秀的 Apache Commons Math库中的课程,其中包括 floor 的新实现。如果它更快,我会感到非常惊讶,但你永远不会知道。
  • 检查您没有运行任何可能干扰 Java 调用本机代码能力的虚拟化技术或类似技术(在一些java.lang.Math功能中使用,包括Math.floor()
于 2012-08-21T06:41:46.033 回答
4

首先:您的分析器显示您在 floor 函数中花费了 99% 的 cpu 时间。这并不表示地板很慢。如果你除了 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;
    }

}

于 2012-08-21T06:28:43.340 回答
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
于 2012-08-21T07:23:18.067 回答
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);
}

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

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

于 2021-06-01T18:58:25.587 回答