在Java中,Math.max(a,b)和(a>b)?a:b哪个更快?

31

在Java中,哪一个更快,为什么?

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

(这是一个面试问题。)


23
我想知道面试官问这样一个问题的目的是什么。 - Seth
18
我非常讨厌人们在面试时问语言律师式的问题。有人真的认为你能否成为高效的程序员与你是否能记住语言规范的细枝末节有着密切相关吗? - dsimcha
8
对于这个问题的正确回答是(来自编译器背景),取决于优化器选择如何处理函数调用。在不知道1.执行上下文和2.优化器对调用参数的首选内联方法的情况下,无法确定上述调用的速度优化。可以进行有根据的猜测(如下所述),但它们只是猜测。我同意@dsimcha的观点,这些问题实际上并不能说明你的能力。 - GrayWizardx
5
我可以为你编写一个 JVM,其中一个比另一个明显更快,或者反过来 ;) - bertolami
5
@Seth: 我希望面试官是在判断被面试者是否沉迷于无意义的微观优化。 - Tom Hawtin - tackline
显示剩余8条评论
7个回答

30

这里是Java中Math.max()代码:

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

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

(说实话,如果你在如此低级别上担心速度改进,那么你可能在你的代码中有更大的问题。)


1
实际上,问题中给出的代码仍然会在ab相等时分支,这会使情况变得更糟。我知道有人会谈到方法查找,但是,老实说,如果您关心这个问题,请不要使用Java。当有人看到Math.max时,他们知道你在做什么,这才是最重要的。 - Sinan Ünür
2
为什么基于 a == b 进行分支会更糟糕?假设 ab 是真正随机的整数,那么 a > b 分支的频率不会比 a >= b 少吧? - Thomas Eding
4
当你查看字节码时,可以看到条件运算符始终会进行分支,因此使用>=而不是=只会在值相等时走另一条路线。当前的Java虚拟机可以实现惊人的性能,即使在低级操作方面,有时也很难被C或ASM超越。如果您知道要避免什么,高性能和Java不一定再是矛盾的。 - x4u
@x4u 好的,谢谢你提供的信息。当时我为了感谢 @jjnguy 花时间查找源代码而给他点了赞。无论如何,我的观点并不是 Java 很慢,而是除非你试图避免某些可怕的错误或其他疯狂的事情,否则担心内联和调用静态方法之间的速度差异毫无意义。 - Sinan Ünür

29

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


6
编译器不会内联JVM指令,JIT分析器会内联机器代码 :) - BlueRaja - Danny Pflughoeft
@BlueRaja:我已经编辑了我的回答,因为从技术上讲你是对的(我想),但在问题的背景下这只是一个小细节。 - dsimcha
2
据我所知,JIT 在问题块被执行多次(成千上万次?)之前不会实际执行任何优化。在性能问题方面(特别是在 Java 中),唯一安全的答案是:“这取决于……” - TMN
@TMN:好的,但是假设您正在寻找实际的回答而不是法律上的回答,为什么您会优化函数调用开销,而这个函数被调用的频率非常低呢? - dsimcha
由于这个问题是在面试中提出的,我认为他们可能正在寻找一种“法律术语”类型的回答。一个可能的情况是,如果你正在尝试优化一个最后机会的异常处理程序,它不应该被调用,但如果它被调用了,它必须快速执行。我只是编了一个例子;当然,在现实世界中,你不需要担心像这样微不足道的优化。 - TMN
1
不幸的是,我认为这归结于优化器识别哪些特殊情况。例如,假设有一条单机指令可以执行max函数?优化器识别了对Math.max()的调用并将其替换为该机器指令,但它没有识别(a>b)?a:b。或者反过来,优化器能够处理Math.max()的最佳方法是将其转换为(a>b)?a:b,而这种转换需要时间。 - Edward Falk

13

性能问题总是需要在你开始推测之前进行测试:

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 );
}

使用 Math.max() 时,这在我的机器上需要6秒,而使用 ?: 则需要3.2秒,在最新的1.6.1 x64服务器Sun JVM上。因此,?: 实际上更快。尽管我们希望JIT(即时编译器)能够捕获所有东西,但它们仍然不能做到。

编辑:出于好奇,我还尝试在同一台机器上使用32位客户端JVM 1.6.1运行此代码,并且两个版本都需要7秒!所以问题可能不在于方法调用无法内联化,而在于服务器JIT似乎可以针对这个特定的测试用例进行一些额外的优化,而无法检测到是否涉及方法调用。


1
这是每个循环迭代少于一个周期的差异。可能不太显著。 - Tom Hawtin - tackline
哦,当然Math.max的定义与您的替代方案略有不同,并且您几乎总是遵循相同的路径(当您不这样做时可能会导致罕见的非最优化情况) - Tom Hawtin - tackline
我在条件中使用了>=>,但执行时间仍然保持不变。 - x4u

6
不要依赖于猜测。相反,基准测试您的特定用例。
许多其他答案中容易被忽视的细节:
虽然您可以看到 Math.max 的Java源代码,但实际上并非总是如此。这种方法在几乎每个JRE中都有一个固有版本。请参见 JDK7中Hotspot的源代码, vmSymbols.hpp 以获取此类内置信息列表。
据我所知,当Hotspot看到 max min 语句时,它会尝试多种优化,特别是针对优化例如 arraycopy 。在其他人中,它实际上会将 Math.max(same,same)优化掉。
在其他情况下,它可能并没有优化很多;(a<=b)?a:b实际上可能会更快。我一直在进行基准测试,确实经常发现这样做更快。但是,结果可能因情况而异,并且如果Hotspot可以更好地优化其中一个,则肯定取决于上下文。这也会因热点版本而异...

我的基准测试结果显示,十年后,“如果(a>b)a=b;”比“a=Math.max(a,b);”更快,这让我感到意外。所以,是的,基准测试大多数时候是最好的方法,尽管当然这种微观优化的基准测试结果可能在不同系统间有很大差异。 - N4ppeL

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

什么?我不明白那是怎么回事。比较运算符只适用于原始数据类型,因此行为是明确定义的(除非我漏掉了什么)。请解释一下。 - Thomas Eding
2
你缺少了double、float、Double.NaN和Float.NaN。 - Mark Thornton
正零和负零之间的区别。 - jarnbjo

3

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


-2
不一样。当你写 (a > b) ? a : b 时,你没有额外的函数调用,所以它会更快。这相当于在 C++ 中进行内联。 但是在现实生活中,这不会有任何区别。Math.max(a,b) 更易读,所以我会使用它。

任何函数调用都会增加开销,即使它不是虚函数。如果您检查机器代码,您会发现当函数被调用时,参数被打包到堆栈上;当函数返回时,结果值从堆栈中提取。所有这些都将消耗CPU周期。 - XMLDUDE
2
你所描述的正是内联优化旨在解决的问题。像max这样的小函数几乎总是被任何好的编译器或JIT内联。 - dsimcha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接