【发布时间】:2016-10-20 22:50:48
【问题描述】:
下面的代码分别调用了两个简单的函数 100 亿次。
public class PerfTest {
private static long l = 0;
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b");
long time1 = System.currentTimeMillis();
for (long i = 0; i < 1E10; i++) {
func1("a", "b");
}
long time2 = System.currentTimeMillis();
for (long i = 0; i < 1E10; i++) {
func2(list);
}
System.out.println((time2 - time1) + "/" + (System.currentTimeMillis() - time2));
}
private static void func1(String s1, String s2) { l++; }
private static void func2(List<String> sl) { l++; }
}
我的假设是这两个调用的性能将接近相同。如果有的话,我会猜到传递两个参数会比传递一个稍慢。鉴于所有参数都是对象引用,我没想到一个列表会产生任何影响。
我已经多次运行测试,典型的结果是“12781/30536”。换句话说,使用两个字符串的调用需要 13 秒,使用列表的调用需要 30 秒。
对这种性能差异的解释是什么?或者这是一个不公平的测试?我试过切换这两个调用(以防它是由于启动影响),但结果是一样的。
更新
这不是一个公平的测试,原因有很多。然而,它确实展示了 Java 编译器的真实行为。请注意以下两个添加内容来证明这一点:
- 在函数中添加表达式
s1.getClass()和sl.getClass()使两个函数调用的性能相同 - 使用
-XX:-TieredCompilation运行测试也会使两个函数调用执行相同的操作
对此行为的解释在下面接受的答案中。 @apangin 的答案非常简短的总结是 func2 没有被热点编译器内联,因为它的参数类(即List)没有被解析。强制解析类(例如使用getClass)会导致它被内联,从而显着提高其性能。正如答案中所指出的,未解析的类不太可能出现在实际代码中,这使得该代码成为不切实际的边缘情况。
【问题讨论】:
-
您能添加您期望的内容吗?为什么?
-
@ChiefTwoPencils 在上面添加了一个段落。
-
我没有投票关闭,但除非有人愿意破解运行时以查看特定的编译优化,否则大多数性能问题并不是很有用(尽管它们可能很有趣/有趣)——答案可能会因版本而异。在这种情况下,我只是假设 JVM 发现编译或记住两个参数调用比数组调用更容易,但说真的——只写最易读的东西!另请注意,可读性最高的版本通常是 JVM 优化得最好的版本。
-
@BillK 我完全同意代码的清晰度比性能更重要。但我很好奇为什么两者之间存在如此显着的差异,我发帖是因为我希望过去有人对此进行过调查并做出解释。
-
ps。对于此调用: func1("a", "b"); java 可能不会传递任何东西,实际上它可能会内联整个东西并只返回一个常量。 Java 在运行时优化的能力使得编写一个好的性能测试非常困难(这本身就是一个很好的迹象,说明为什么 java 的性能如此出色)
标签: java performance