性能测试独立于迭代次数

时间:2012-08-24 09:37:05

标签: java performance

尝试回答此故障单:What is the difference between instanceof and Class.isAssignableFrom(...)?

我做了一次性能测试:

class A{}
class B extends A{}

A b = new B();

void execute(){
  boolean test = A.class.isAssignableFrom(b.getClass());
  // boolean test = A.class.isInstance(b);
  // boolean test = b instanceof A;
}

@Test
public void testPerf() {
  // Warmup the code
  for (int i = 0; i < 100; ++i)
    execute();

  // Time it
  int count = 100000;
  final long start = System.nanoTime();
  for(int i=0; i<count; i++){
     execute();
  }
  final long elapsed = System.nanoTime() - start;
System.out.println(count+" iterations took " + TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms.);
}

哪位给了我:

  • A.class.isAssignableFrom(b.getClass()):100000次迭代 15ms
  • A.class.isInstance(b):100000次迭代耗时 12ms
  • b instanceof A :100000次迭代耗时 6ms

但是玩迭代次数,我可以看到性能是不变的。对于Integer.MAX_VALUE:

  • A.class.isAssignableFrom(b.getClass()):2147483647次迭代 15ms
  • A.class.isInstance(b):2147483647次迭代 12ms
  • b instanceof A :2147483647次迭代 6ms

认为这是编译器优化(我使用JUnit运行此测试),我将其更改为:

@Test
public void testPerf() {
    boolean test = false;

    // Warmup the code
    for (int i = 0; i < 100; ++i)
        test |= b instanceof A;

    // Time it
    int count = Integer.MAX_VALUE;
    final long start = System.nanoTime();
    for(int i=0; i<count; i++){
        test |= b instanceof A;
    }
    final long elapsed = System.nanoTime() - start;
    System.out.println(count+" iterations took " + TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms. AVG= " + TimeUnit.NANOSECONDS.toMillis(elapsed/count));

    System.out.println(test);
}

但性能仍然是“独立”的迭代次数。 有人能解释一下这种行为吗?

3 个答案:

答案 0 :(得分:5)

  1. 一百次迭代不足以进行预热。默认的编译阈值是10000次迭代(一百次以上),所以最好至少超过该阈值。
  2. 一旦触发编译,世界就不会停止;编译发生在后台。这意味着它的影响只有在稍微延迟后才能开始观察。
  3. 有足够的空间来优化测试,使整个循环折叠成最终结果。这可以解释常数。
  4. 无论如何,我总是通过外部方法调用内部方法10次来做基准测试。内部方法根据需要进行大量迭代,例如10,000或更多,以使其运行时间上升至少几十毫秒。我甚至不打扰nanoTime,因为如果微秒精度对你来说很重要,那只是测量时间间隔太短的迹象。

    当你这样做时,你可以轻松地让JIT在替换解释版本后执行内部方法的编译版本。另一个好处是你可以确保内部方法的时间稳定。

答案 1 :(得分:3)

如果您想要对简单函数进行真正的基准测试,则应使用微基准测试工具,例如Caliper。尝试制定自己的基准测试会更简单。

答案 2 :(得分:1)

JIT编译器可以消除没有任何内容的循环。这可以在10,000次迭代后触发。

我怀疑你的时间是JIT检测到循环没有做任何事情并将其删除所需的时间。这将比进行10,000次迭代的时间稍长一些。