下面的代码分别调用两个简单的函数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++; }
}
Run Code Online (Sandbox Code Playgroud)
我的假设是这两个电话的表现几乎相同.如果有什么我会猜到传递两个参数会比传递一个稍慢.鉴于所有参数都是对象引用,我并不期望有一个列表可以产生任何差异.
我已多次运行测试,典型结果为"12781/30536".换句话说,使用两个字符串的呼叫需要13秒,使用列表的呼叫需要30秒.
这种性能差异的解释是什么?或者这是不公平的考验?我已经尝试切换两个调用(如果它是由于启动效果)但结果是相同的.
更新
由于许多原因,这不是一个公平的测试.但它确实展示了Java编译器的真实行为.请注意以下两个补充说明:
s1.getClass()和sl.getClass()函数使两个函数调用相同-XX:-TieredCompilation也会使两个函数调用执行相同的操作对此行为的解释在下面接受的答案中.@ apangin的答案的简短摘要func2是热点编译器没有内联,因为它的参数类(即List)没有被解析.强制类的解析(例如使用getClass)会使其内联,从而显着提高其性能.正如答案中指出的那样,未解决的类不太可能出现在实际代码中,这使得此代码成为不切实际的边缘情况.
apa*_*gin 19
基准是不公平的,然而,它揭示了一个有趣的效果.
正如Sotirios Delimanolis所注意到的,性能差异是func1由HotSpot编译器内联的事实造成的,而func2不是.原因是func2类型的参数,在执行基准测试期间List从未解决过的类.
请注意,List实际上并未使用类:没有调用List方法,没有声明List类型的字段,没有类强制转换,也没有执行通常会导致类解析的其他操作.如果List在代码中的任何位置添加类的使用,func2则将内联.
影响编译策略的另一个环节是方法的简单性.这很简单,JVM决定在第1层编译它(C1没有进一步的优化).如果它是用C2编译的,那么List类将被解析.尝试运行-XX:-TieredCompilation,您将看到func2成功内联,并执行速度最快func1.
手动编写逼真的微基准测试是一项非常困难的工作.有很多方面可能导致令人困惑的结果,例如内联,死代码消除,堆栈替换,配置文件污染,重新编译等.这就是为什么强烈建议使用适当的基准测试工具,如JMH.手写的基准测试很容易欺骗JVM.特别是,真正的应用程序不太可能拥有从未使用过的类的方法.
| 归档时间: |
|
| 查看次数: |
612 次 |
| 最近记录: |