【问题标题】:Is Math.max(a,b) or (a>b)?a:b faster in Java?Math.max(a,b) or (a>b)?a:b 在 Java 中更快吗?
【发布时间】:2011-01-07 09:33:18
【问题描述】:

Java 中哪个更快,为什么?

  1. Math.max(a,b)
  2. (a>b)?a:b

(这是在采访中被问到的。)

【问题讨论】:

  • 不知道面试官问这样的问题希望得到什么。
  • 我真的很讨厌人们在面试时问语言律师问题。有没有人真的认为你成为一名高效程序员的能力与你记忆语言规范细节的能力密切相关?
  • 对这个问题的正确回答是(来自编译器背景)它取决于优化器选择对函数调用做什么。在不知道 1. 执行上下文和 2. 优化器首选内联方法的调用参数的情况下,NO WAY 可以确定上述调用的速度优化。可以做出有根据的猜测(如下所示),但它们只是猜测。我同意@dsimcha 这些问题并不能真正说明你的能力。
  • 我可以给你写一个jvm,其中一个比另一个快得多,反之亦然;)
  • @Seth:我希望面试官试图确定面试官是否沉迷于毫无意义的微优化。

标签: java core


【解决方案1】:

Math.max(a, b) 是一个静态函数(意味着没有虚拟调用开销)并且可能会被 JVM 内联到与 (a > b) ? a : b 相同的指令。

【讨论】:

  • 编译器不内联JVM指令,JIT分析器内联机器码:)
  • @BlueRaja:我已经编辑了我的回复,因为从技术上讲你是对的(我认为),但在问题的背景下这确实是一个小细节。
  • IIRC,JIT 实际上不会执行任何优化,直到有问题的块被执行了很多(数千?)次。性能问题(尤其是在 Java 中)的唯一安全答案是:“这取决于...”
  • @TMN:好的,但是假设您正在寻找实用响应而不是法律响应,那么您为什么要优化函数调用开销来调用被调用的东西不经常?
  • 不幸的是,我认为这归结为优化器识别的特殊情况。例如,假设有一条机器指令可以做最大值?优化器识别出对 Math.max() 的调用并将其替换为该机器指令,但它识别(a>b)?a:b。或者相反,它可以用 Math.max() 做的最好的事情就是将其转换为(a>b)?a:b,而这种转换需要时间。
【解决方案2】:

Here 是 Java 中 Math.max() 代码:

public static int max(int a, int b) {
    return (a >= b) ? a : b;
}

因此,代码的速度可能(几乎)完全相同。

(老实说,如果您担心速度提升如此之低,那么您的代码中可能会遇到更大的问题。)

【讨论】:

  • 实际上,当ab 相等时,给出问题的代码仍然分支,这会使情况变得更糟。我知道有人会说一些关于方法查找的事情,但老实说,如果你关心它,就不要使用 Java。当有人看到Math.max 时,他们知道你在做什么,这才是最重要的。
  • 为什么在a == b 上分支会更糟?假设ab 是真正随机的int s。那么a > b 的分支不会和a >= b 一样频繁吗?
  • @Sinan:当您查看字节码时,您可以看到条件运算符总是分支,因此使用>= 而不是= 只会使它在值相等时使用另一条路线.当前的 Java VM 也可以实现令人惊叹的性能,即使是 C 或 ASM 有时也难以击败的低级操作。如果您知道要避免什么,高性能和 Java 就不再是矛盾了。
  • @x4u 好的。感谢您的信息。我当时支持@jjnguy 的回答,让他花时间查找源代码中的内容。无论如何,我的观点不是 Java 很慢,而是绝对没有必要担心内联和调用静态方法之间的速度差异,除非你试图避免一些可怕的错误或其他一些疯狂的事情。
【解决方案3】:

在您开始推测之前,性能问题总是需要测试:

public static void maxtest()
{
    int res = 0;
    for( int idx = 0; --idx != 0; )
        // res = ( res > idx ) ? res : idx;
        res = Math.max( res, idx );
    System.out.println( "res: " + res );
}

这在我的机器上运行 6 秒,Math.max() 和 3.2 秒,?: 在最新的 1.6.1 x64 服务器 Sun JVM 上。所以?: 实际上更快。与我们希望寄托在 JIT 中的所有希望相反,当 JIT 仍然无法捕获所有内容时,它们已经变得非常棒了。

编辑:出于好奇,我还在同一台机器上使用 32 位客户端 JVM 1.6.1 尝试了这段代码,并且这两个版本都在 7 秒内运行!因此,可能不是没有内联的方法调用,而是服务器 JIT 似乎能够为这个特定的测试用例做一些额外的优化,当涉及到方法调用时它无法检测到。

【讨论】:

  • 即每次循环迭代的差异小于一个循环。可能并不重要。
  • 哦,当然Math.max 的定义与您的替代方案略有不同,而且您几乎总是遵循相同的路径(如果不这样做可能会导致罕见的去优化)
  • 我在条件下使用>=>运行它,执行时间保持不变。
【解决方案4】:

不要依赖推测。相反,基准测试您的特定用例。

许多其他答案中一些容易被忽视的细节:

虽然您可以看到Math.max 的Java 源代码,但实际上并不总是将使用它。这种方法在几乎每个 JRE 中都有一个内在版本。有关此类内在函数的列表,请参阅 source code of Hotspot in JDK7, vmSymbols.hpp

据我所知,Hotspot 在看到maxmin 语句时会尝试一些优化;特别是优化,例如arraycopy。其中,它实际上会优化Math.max(same, same)

但是,在其他情况下,它可能不会优化太多; (a<=b)?a:b 实际上可能会更快。我一直在进行基准测试,实际上我经常发现这更快。但是 YMMV,如果 Hotspot 可以更好地优化一个或另一个,这绝对取决于上下文。它也会因热点版本而异...

【讨论】:

    【解决方案5】:

    如果我在面试中问了这样一个问题,我希望应聘者会告诉我,对于所有可能的 a 和 b 类型,这两个表达式可能不会给出相同的结果。

    【讨论】:

    • 什么?我不明白这是怎么回事。比较运算符仅适用于原语,因此行为定义明确(除非我遗漏了什么)。请解释一下。
    • 你缺少 double、float、Double.NaN 和 Float.NaN。
    • 还有正负零的区别。
    【解决方案6】:

    原始问题未指定参数的类型。这很重要,因为浮点参数的最大值(和最小值)的定义更复杂。对于浮点数(double 或 float),Math.max 方法可能会更慢,但如果参数之一是 NaN,它也可能返回不同的结果。

    【讨论】:

      【解决方案7】:

      不一样。当您编写(a > b) ? a : b 时,您没有额外的函数调用,因此它会更快。它相当于 C++ 中的内联。 但这不会对现实生活产生任何影响。 Math.max(a,b) 更具可读性,所以我会使用它。

      【讨论】:

      • 任何函数调用都会增加开销,即使不是虚拟的。如果你检查机器代码,你会发现当函数被调用时,参数被打包在堆栈上;当函数返回时,从堆栈中提取结果值。所有这些都会消耗 CPU 周期。
      • 您所描述的正是内联优化旨在解决的问题。像 max 这样的小函数几乎总是被任何体面的编译器或 JIT 内联。
      猜你喜欢
      • 2021-11-28
      • 2017-01-11
      • 2018-01-29
      • 2011-08-19
      • 1970-01-01
      • 2016-06-02
      • 2015-06-15
      • 1970-01-01
      • 2019-10-27
      相关资源
      最近更新 更多