3

试图回答这张票:instanceof 和 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 次迭代耗时15 毫秒
  • A.class.isInstance(b) : 100000 次迭代耗时12ms
  • b instanceof A : 100000 次迭代耗时6ms

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

  • A.class.isAssignableFrom(b.getClass()) 2147483647 次迭代耗时15 毫秒
  • 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);
}

但性能仍然与迭代次数“无关”。有人可以解释这种行为吗?

4

3 回答 3

5
  1. 一百次迭代不足以进行热身。默认编译阈值为 10000 次迭代(一百倍以上),因此最好至少超过该阈值。
  2. 一旦编译被触发,世界就不会停止;编译在后台进行。这意味着它的效果只有在稍有延迟后才会开始显现。
  3. 有足够的空间来优化您的测试,以使整个循环折叠成最终结果。这将解释常数。

无论如何,我总是通过让外部方法调用内部方法大约 10 次来进行基准测试。内部方法根据需要进行大量迭代,例如 10,000 次或更多,以使其运行时间上升到至少几十毫秒。我什至不打扰,nanoTime因为如果微秒精度对您很重要,那只是测量时间间隔太短的标志。

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

于 2012-08-24T09:40:02.757 回答
3

如果你想对一个简单的函数做一个真正的基准测试,你应该使用一个微基准测试工具,比如Caliper。尝试制作自己的基准会简单得多。

于 2012-08-24T09:43:00.570 回答
1

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

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

于 2012-08-24T09:49:56.933 回答