newInstance vs jdk-9 / jdk-8和jmh中的new [英] newInstance vs new in jdk-9/jdk-8 and jmh

查看:93
本文介绍了newInstance vs jdk-9 / jdk-8和jmh中的new的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这里看到很多线程比较并尝试回答哪个更快: newInstance new operator

I've seen a lot of threads here that compare and try to answer which is faster: newInstance or new operator.

查看源代码,似乎 newInstance 应该慢得多,我的意思是它做了很多安全检查并使用反射。而且我决定先测量一下jdk-8。以下是使用 jmh 的代码。

Looking at the source code, it would seem that newInstance should be much slower, I mean it does so many security checks and uses reflection. And I've decided to measure, first running jdk-8. Here is the code using jmh.

@BenchmarkMode(value = { Mode.AverageTime, Mode.SingleShotTime })
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)   
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)    
@State(Scope.Benchmark) 
public class TestNewObject {
    public static void main(String[] args) throws RunnerException {

        Options opt = new OptionsBuilder().include(TestNewObject.class.getSimpleName()).build();
        new Runner(opt).run();
    }

    @Fork(1)
    @Benchmark
    public Something newOperator() {
       return new Something();
    }

    @SuppressWarnings("deprecation")
    @Fork(1)
    @Benchmark
    public Something newInstance() throws InstantiationException, IllegalAccessException {
         return Something.class.newInstance();
    }

    static class Something {

    } 
}

我不认为这里有很大的惊喜(JIT做了很多优化,使这个差异不是那么大):

I don't think there are big surprises here (JIT does a lot of optimizations that make this difference not that big):

Benchmark                  Mode  Cnt      Score      Error  Units
TestNewObject.newInstance  avgt    5      7.762 ±    0.745  ns/op
TestNewObject.newOperator  avgt    5      4.714 ±    1.480  ns/op
TestNewObject.newInstance    ss    5  10666.200 ± 4261.855  ns/op
TestNewObject.newOperator    ss    5   1522.800 ± 2558.524  ns/op

热门代码的差异大约是 2x ,单次拍摄时间差得多。

The difference for the hot code would be around 2x and much worse for single shot time.

现在我切换到jdk-9(构建157以防万一)并运行相同的代码。
和结果:

Now I switch to jdk-9 (build 157 in case it matters) and run the same code. And the results:

 Benchmark                  Mode  Cnt      Score      Error  Units
 TestNewObject.newInstance  avgt    5    314.307 ±   55.054  ns/op
 TestNewObject.newOperator  avgt    5      4.602 ±    1.084  ns/op
 TestNewObject.newInstance    ss    5  10798.400 ± 5090.458  ns/op
 TestNewObject.newOperator    ss    5   3269.800 ± 4545.827  ns/op

这是热门代码中百分之五十的差异。我正在使用最新的jmh版本(1.19.SNAPSHOT)。

That's a whooping 50x difference in hot code. I'm using latest jmh version (1.19.SNAPSHOT).

在测试中再添加一个方法后:

After adding one more method to the test:

@Fork(1)
@Benchmark
public Something newInstanceJDK9() throws Exception {
    return Something.class.getDeclaredConstructor().newInstance();
}

以下是整体结果n jdk-9:

Here are the overall results n jdk-9:

TestNewObject.newInstance      avgt    5    308.342 ±   107.563  ns/op
TestNewObject.newInstanceJDK9  avgt    5     50.659 ±     7.964  ns/op
TestNewObject.newOperator      avgt    5      4.554 ±     0.616  ns/op    

有人可以了解为什么有如此大的差异

推荐答案

首先,问题与模块无关系统(直接)。

我注意到即使使用JDK 9,第一次热身迭代 newInstance 与JDK 8一样快。

I noticed that even with JDK 9 the first warmup iteration of newInstance was as fast as with JDK 8.

# Fork: 1 of 1
# Warmup Iteration   1: 10,578 ns/op    <-- Fast!
# Warmup Iteration   2: 246,426 ns/op
# Warmup Iteration   3: 242,347 ns/op

这意味着JIT编译中出现了问题。

-XX:+ PrintCompilation 确认在第一次迭代后重新编译基准:

This means something has broken in JIT compilation.
-XX:+PrintCompilation confirmed that the benchmark was recompiled after the first iteration:

10,762 ns/op
# Warmup Iteration   2:    1541  689   !   3       java.lang.Class::newInstance (160 bytes)   made not entrant
   1548  692 %     4       bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub @ 13 (56 bytes)
   1552  693       4       bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub (56 bytes)
   1555  662       3       bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub (56 bytes)   made not entrant
248,023 ns/op

然后 -XX:+ UnlockDiagnosticVMOptions -XX:+ PrintInlining 指向内联问题:

1577  667 %     4       bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub @ 13 (56 bytes)
                           @ 17   bench.NewInstance::newInstance (6 bytes)   inline (hot)
            !                @ 2   java.lang.Class::newInstance (160 bytes)   already compiled into a big method

已编译成大方法消息表示编译器无法内联 Class.newInstance 调用,因为被调用者的编译大小大于 InlineSmallCode 值(默认为2000)。

"already compiled into a big method" message means that the compiler has failed to inline Class.newInstance call because the compiled size of the callee is larger than InlineSmallCode value (which is 2000 by default).

当我用重新计算基准时-XX:InlineSmallCode = 2500 ,它再次变快。

Benchmark                Mode  Cnt  Score   Error  Units
NewInstance.newInstance  avgt    5  8,847 ± 0,080  ns/op
NewInstance.operatorNew  avgt    5  5,042 ± 0,177  ns/op

你知道,JDK 9现在有 G1作为默认GC 。如果我回归并行GC,即使使用默认的 InlineSmallCode ,基准测试也会很快。

You know, JDK 9 now has G1 as the default GC. If I fall back to Parallel GC, the benchmark will also be fast even with the default InlineSmallCode.

重新运行JDK 9基准 -XX:+ UseParallelGC

Rerun JDK 9 benchmark with -XX:+UseParallelGC:

Benchmark                Mode  Cnt  Score   Error  Units
NewInstance.newInstance  avgt    5  8,728 ± 0,143  ns/op
NewInstance.operatorNew  avgt    5  4,822 ± 0,096  ns/op

G1需要在对象存储发生时设置一些障碍,这就是为什么编译后的代码变得有点大,所以 Class.newInstance 超过默认 InlineSmallCode 限制。编译 Class.newInstance 的另一个原因是,在JDK 9中稍微重写了反射代码。

G1 requires to put some barriers whenever an object store happens, that's why the compiled code becomes a bit larger, so that Class.newInstance exceeds the default InlineSmallCode limit. Another reason why compiled Class.newInstance has become larger is that the reflection code had been slightly rewritten in JDK 9.


TL; DR JIT未能内联 Class.newInstance ,因为 InlineSmallCode 已超出限额。由于JDK 9中反射代码的更改以及默认GC已更改为G1, Class.newInstance 的编译版本变得更大。

TL;DR JIT has failed to inline Class.newInstance, because InlineSmallCode limit has been exceeded. The compiled version of Class.newInstance has become larger due to changes in reflection code in JDK 9 and because the default GC has been changed to G1.

这篇关于newInstance vs jdk-9 / jdk-8和jmh中的new的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆