我正在开发一个应用程序,其中一个设计方法涉及到大量使用instanceof操作符。虽然我知道OO设计通常试图避免使用instanceof,但那是另一回事,这个问题纯粹与性能有关。我想知道是否对性能有影响?和==一样快吗?

例如,我有一个有10个子类的基类。在接受基类的单个函数中,我检查类是否是子类的实例并执行一些例程。

我想到的另一种解决方法是使用“type id”整数原语,并使用位掩码来表示子类的类别,然后对子类“type id”与表示类别的常量掩码进行位掩码比较。

instanceof是否被JVM优化得更快?我想坚持使用Java,但应用程序的性能至关重要。如果有人曾经在这条路上走过,可以提供一些建议,那就太棒了。我是不是太挑剔了,或者专注在错误的地方去优化?


当前回答

我认为在本页提交一个反例来反驳“instanceof”的普遍共识是值得的。我发现我在一个内循环中有一些代码(在一些历史性的优化尝试中)

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

在SingleItem上调用head()返回的值不变。将代码替换为

seq = seq.head();

让我从269毫秒加速到169ms,尽管事实上在循环中发生了一些相当繁重的事情,比如字符串到双精度转换。当然,加速可能更多是由于消除了条件分支,而不是消除了操作符本身的实例;但我觉得值得一提。

其他回答

我也更喜欢枚举方法,但我将使用抽象基类强制子类实现getType()方法。

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}

Instanceof非常快。它可以归结为用于类引用比较的字节码。在一个循环中尝试几百万个实例,自己看看。

现代JVM/JIT编译器已经消除了大多数传统的“慢”操作对性能的影响,包括instanceof、异常处理、反射等。

正如Donald Knuth所写的,“我们应该忘记小的效率,大约97%的时候:过早的优化是万恶之源。”instanceof的性能可能不是问题,所以在确定问题所在之前,不要浪费时间想出奇异的解决方案。

我基于jmh-java-benchmark- prototype:2.21编写了一个性能测试。JDK为openjdk, version为1.8.0_212。测试机器是mac pro。 测试结果为:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

结果表明:getClass优于instanceOf,这与其他测试结果相反。然而,我不知道为什么。

测试代码如下:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

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

将决定性能影响的项目有:

The number of possible classes for which the instanceof operator could return true The distribution of your data - are most of the instanceof operations resolved in the first or second attempt? You'll want to put your most likely to return true operations first. The deployment environment. Running on a Sun Solaris VM is significantly different than Sun's Windows JVM. Solaris will run in 'server' mode by default, while Windows will run in client mode. The JIT optimizations on Solaris, will make all method access able the same.

我为四种不同的分派方法创建了一个微基准测试。Solaris的结果如下所示,数值越小越快:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067