【问题标题】:Why isn't there measureable difference between new Something() and Something.class.newinstance()?为什么 new Something() 和 Something.class.newinstance() 之间没有可测量的差异?
【发布时间】:2017-03-14 23:34:23
【问题描述】:

我正在运行以下实验,并惊讶地发现两次运行之间没有可测量的差异:

public static void main(String[] args) throws Exception {
    long count = 1_000_000_0L;
    long measureCount = 1000L;


    // Measurement 1
    SpecificRecord tp1 = null;
    List<Long> times = new ArrayList<>();
    for (long j=0; j<measureCount; ++j) {
        long timeStart = System.currentTimeMillis();
        for (long i = 0; i < count; ++i) {
            tp1 = new WebPageView();
        }
        times.add(System.currentTimeMillis() - timeStart);
    }
    Stats st = Stats.of(times);
    double avg = st.mean();
    double stdDev = st.populationStandardDeviation();
    times.sort(Long::compareTo);
    int upper = (int)Math.ceil(times.size()/2.0);
    int lower = (int)Math.floor(times.size()/2.0);
    double median = ((times.get(upper))+(times.get(lower)))/2.0;
    System.out.println("avg: "+avg);
    System.out.println("stdDev: "+stdDev);
    System.out.println("median: "+median);
    System.out.println(tp1);


    // Measurement 2
    SpecificRecord tp2 = null;
    List<Long> times2 = new ArrayList<>();
    for (long j=0; j<measureCount; ++j) {
        long timeStart = System.currentTimeMillis();
        for (long i = 0; i < count; ++i) {
            tp2 = WebPageView.class.newInstance();
        }
        times2.add(System.currentTimeMillis() - timeStart);
    }
    Stats st2 = Stats.of(times2);
    double avg2 = st2.mean();
    double stdDev2 = st2.populationStandardDeviation();
    times2.sort(Long::compareTo);
    int upper2 = (int)Math.ceil(times2.size()/2.0);
    int lower2 = (int)Math.floor(times2.size()/2.0);
    double median2 = ((times2.get(upper2))+(times2.get(lower2)))/2.0;
    System.out.println("avg: "+avg2);
    System.out.println("stdDev: "+stdDev2);
    System.out.println("median: "+median2);
    System.out.println(tp2);
  }
}

结果:

avg: 110.63300000000005
stdDev: 47.07256431298379
median: 100.0
{"aid": 0, "uid": 0, "rid": 0, "sid": 0, "d": null, "p": null, "r": null, "f": null, "q": null, "ts": 0}
avg: 101.0840000000001
stdDev: 7.8092857547921835
median: 99.0
{"aid": 0, "uid": 0, "rid": 0, "sid": 0, "d": null, "p": null, "r": null, "f": null, "q": null, "ts": 0}

更新1:

你们中的许多人指出,不可能以这种方式对 JVM 进行基准测试,因为大量优化掩盖了 new Something() 和 Something.class.newinstance() 之间的性能差异。

更新2:

用建议的方法重复测试后,结果让我有点惊讶:

Benchmark                   Mode  Cnt   Score   Error  Units
ReflectionTest.newInstance  avgt    5  12.923 ± 0.801  ns/op
ReflectionTest.newOperator  avgt    5  11.524 ± 0.289  ns/op

更新3: 测试代码:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 100, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class ReflectionTest {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(ReflectionTest.class.getSimpleName()).forks(1).build();
        new Runner(opt).run();
    }

    @Benchmark
    public WebPageView newOperator() {
        return new WebPageView();
    }

    @Benchmark
    public WebPageView newInstance() throws InstantiationException, IllegalAccessException {
        return WebPageView.class.newInstance();
    }
}

这个问题仍然没有答案,没有测试可以概述我们使用的类的 class.newInstance() 与 new 之间的任何区别。此类使用的是 Avro SpecificRecord 的实现。

public class WebPageView extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord

【问题讨论】:

  • 您应该知道,由于许多编译器优化,您的基准测试可能不正确。如果您想编写适当的基准测试,请查看以下答案:stackoverflow.com/a/513259/3449268
  • @Istvan 这是你衡量的方式(很快就会有人建议jmh)。但他们在速度上应该有很大不同newInstance 做了很多昂贵的检查。
  • @LewBloch 我已经尝试在我的回答中解决您的问题。
  • @Istvan you're update 2 确实很有趣。答案 wan 使用 jdk-9 运行,但是当我切换到 jdk-8 时,仍然存在差异,但要小得多(就像你的发现一样)。我会进一步研究。
  • @Istvan 事实是我不知道为什么差异如此之大。我真的希望更聪明的人能够阐明一些观点。这是链接:stackoverflow.com/questions/42786629/…

标签: java performance


【解决方案1】:

编辑

测试仍然很好,但它是在 jdk-9 上运行的,事实证明它比 jdk-8 慢得多。

使用 jdk-8 运行,将证明 JIT 做了很多优化,真正的差异在 2x 左右。

使用 jmh 进行的一些测试显示了很大的不同(使用 jdk-9 运行

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@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()).forks(1).build();
        new Runner(opt).run();
    }

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

    @Benchmark
    public Something newInstance() throws InstantiationException, IllegalAccessException {
        return Something.class.newInstance();
    }

    static class Something {

    }
}

而结果,显示出相当大的不同。

Benchmark                  Mode  Cnt    Score    Error  Units
TestNewObject.newInstance  avgt    5  274.070 ± 50.554  ns/op
TestNewObject.newOperator  avgt    5    5.119 ±  3.550  ns/op

这是 44 倍 的速度差异。对newInstance 的调用做了很多(检查源代码)非常昂贵的事情(反射和安全检查)。

虽然使用 new 运算符,但考虑到 TLAB 是分配对象的一种极快的方法。

要提一提的是,newInstance 在 jdk-9 中已被弃用。

【讨论】:

  • 优秀的答案谢谢!另外,当我在编译时不知道需要使用的类时,Jdk-9 中处理这种情况的方法是什么?这可能值得在一个单独的问题中提出。
猜你喜欢
  • 2013-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-23
  • 2015-02-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多