java – 为什么传递两个字符串参数比一个列表参数更有效

下面的代码调用了两个简单的函数,每个函数都是10亿次.

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)会导致内联,这显着提高了其性能.正如答案中指出的那样,未解决的类不太可能发生在实际代码中,这使得该代码成为不切实际的边缘情况.

基准是unfair,但是它显示了一个有趣的效果.

正如Sotirios Delimanolis所注意到的那样,性能差异是由于Hotcot编译器内置func1,而func2不是这样.原因是类型为List的func2参数,即在执行基准测试期间从未被解析的类.

请注意,List类没有被实际使用:no List方法被调用,没有类型List的字段声明,没有类转换,没有其他通常导致类resolution执行的操作.如果在代码中添加List类的任何地方的使用,func2将内联.

影响编译策略的另一个原因是方法的简单性.很简单,JVM已经决定在第1层进行编译(C1,没有进一步优化).如果使用C2编译,则List类将被解析.尝试运行-XX:-TieredCompilation,你会看到func2成功内联,并且执行与func1一样快.

手动编写实际的微型基准测试是一项非常困难的工作.有这么多方面可能导致混乱的结果,例如内联,死代码消除,堆栈替换,配置文件污染,重新编译等.这就是为什么强烈建议使用适当的基准测试工具,如JMH.手写基准可以很容易地欺骗JVM.特别地,真正的应用程序不太可能具有从不使用类的方法.

http://stackoverflow.com/questions/40165758/why-is-passing-two-string-arguments-more-efficient-than-one-list-argument

转载注明原文:java – 为什么传递两个字符串参数比一个列表参数更有效