5

当我遇到一个奇怪的结果时,我正在对对象分配进行一些性能测试。我有以下java代码:

public static long TestMethod(){
    int len = 10000000;
    Object[] obs = new Object[len];
    long t = System.nanoTime();
    for (int i = 0; i < len; i++) {
        obs[i] = new Object();
    }
    return System.nanoTime() - t;
}

public static void main(String... args) throws InterruptedException {
    for(int i = 0; i < 10; i++){
        System.gc();
        System.gc();//Wait for the gc to be finished
        Thread.sleep(1000);
        System.out.println(TestMethod());
    }
}

预期:由于向操作系统请求更大的内存空间和热点增强,第一次调用会比第二次调用慢。但第二和第三几乎是一样的。

观察结果

11284734000
799837000
682304000
304736000
380770000
392786000
374279000
381611000
379174000
407256000

在第三次和第四次迭代之间仍然有相当大的加速。是什么导致了这种加速?在测试其他功能时,如何确保我的测量准确,我是否必须在测量前调用该功能四次以上?

4

2 回答 2

4

是什么导致了这种加速?

它很可能是 JIT 编译,但也可能是代码加载和/或堆热身效应的贡献。

在测试其他功能时,如何确保我的测量准确,我是否必须在测量前调用该功能四次以上?

你需要做这样的事情。没有其他方法可以从您的测量中消除 JVM 预热效应,并且仍然获得具有代表性的结果。为 Java 编写有效的“微基准”很困难,您需要在尝试之前阅读所有问题。从这个开始:如何在 Java 中编写正确的微基准测试?


我还要注意其他几件事:

  1. 你试图从你的测量中消除垃圾收集的成本(我假设这就是你似乎正在做的)似乎失败了。看起来您在执行testMethod. 这将解释您的“稳态”结果中约 7% 的可变性。

  2. 将分配对象的成本与释放它的成本分开可能会给您带来误导性的结果。分配对象的“总”成本包括在回收时将内存归零的成本……这是由垃圾收集器完成的。

  3. 事实上,最有用的度量是分配/收集周期的每个对象的摊销成本。这(令人惊讶的是)取决于 GC 运行时的非垃圾量......这是您的基准测试没有考虑到的。

于 2012-12-30T20:58:59.363 回答
2

正如您所说,有多种因素可以加快函数的执行速度:

  • 最重要的是,JIT可能会在您的应用程序期间发生在不同的程度或时刻(这就是为什么在没有给 JVM 足够的时间进行预热的情况下进行分析会导致误导性结果的原因)
  • 对操作系统的内存请求
  • 为您分配的对象重用堆上的内存

您无法确定每一步何时发生,也无法知道何时将代码片段编译为本机代码,但假设这发生在第一次调用不正确之前,它可能恰好发生在时间介于第三次和第四次迭代或其他。不幸的是,这些细节隐藏在 JVM 实现中。

于 2012-12-30T20:47:55.267 回答