Parler en termes de performances:
TL; DR
Utilisez isInstance ou instanceof qui ont des performances similaires. isAssignableFrom est légèrement plus lent.
Trié par performance:
- isInstance
- instanceof (+ 0,5%)
- isAssignableFrom (+ 2,7%)
Basé sur un benchmark de 2000 itérations sur JAVA 8 Windows x64, avec 20 itérations de préchauffage.
En théorie
En utilisant une visionneuse de bytecode douce, nous pouvons traduire chaque opérateur en bytecode.
Dans le contexte de:
package foo;
public class Benchmark
{
public static final Object a = new A();
public static final Object b = new B();
...
}
JAVA:
b instanceof A;
Bytecode:
getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A
JAVA:
A.class.isInstance(b);
Bytecode:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Class isInstance((Ljava/lang/Object;)Z);
JAVA:
A.class.isAssignableFrom(b.getClass());
Bytecode:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Object getClass(()Ljava/lang/Class;);
invokevirtual java/lang/Class isAssignableFrom((Ljava/lang/Class;)Z);
En mesurant le nombre d'instructions de bytecode utilisées par chaque opérateur, nous pouvons nous attendre à ce que instanceof et isInstance soient plus rapides que isAssignableFrom . Cependant, les performances réelles ne sont PAS déterminées par le bytecode mais par le code machine (qui dépend de la plateforme). Faisons un micro benchmark pour chacun des opérateurs.
La référence
Crédit: Comme conseillé par @ aleksandr-dubinsky, et merci à @yura d'avoir fourni le code de base, voici un benchmark JMH (voir ce guide de réglage ):
class A {}
class B extends A {}
public class Benchmark {
public static final Object a = new A();
public static final Object b = new B();
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testInstanceOf()
{
return b instanceof A;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testIsInstance()
{
return A.class.isInstance(b);
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testIsAssignableFrom()
{
return A.class.isAssignableFrom(b.getClass());
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(TestPerf2.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(2000)
.forks(1)
.build();
new Runner(opt).run();
}
}
A donné les résultats suivants (le score est un nombre d'opérations dans une unité de temps , donc plus le score est élevé, mieux c'est):
Benchmark Mode Cnt Score Error Units
Benchmark.testIsInstance thrpt 2000 373,061 ± 0,115 ops/us
Benchmark.testInstanceOf thrpt 2000 371,047 ± 0,131 ops/us
Benchmark.testIsAssignableFrom thrpt 2000 363,648 ± 0,289 ops/us
Attention
- la référence est dépendante de la JVM et de la plateforme. Puisqu'il n'y a pas de différences significatives entre chaque opération, il pourrait être possible d'obtenir un résultat différent (et peut-être un ordre différent!) Sur une version JAVA et / ou des plates-formes différentes comme Solaris, Mac ou Linux.
- l'indice de référence compare les performances de "est B une instance de A" lorsque "B étend A" directement. Si la hiérarchie des classes est plus profonde et plus complexe (comme B étend X qui étend Y qui étend Z qui étend A), les résultats peuvent être différents.
- il est généralement conseillé d'écrire le code en choisissant d'abord l'un des opérateurs (le plus pratique), puis de profiler votre code pour vérifier s'il existe un goulot d'étranglement des performances. Peut-être que cet opérateur est négligeable dans le contexte de votre code, ou peut-être ...
- par rapport au point précédent,
instanceof
dans le contexte de votre code pourrait être optimisé plus facilement qu'un isInstance
par exemple ...
Pour vous donner un exemple, prenez la boucle suivante:
class A{}
class B extends A{}
A b = new B();
boolean execute(){
return A.class.isAssignableFrom(b.getClass());
// return A.class.isInstance(b);
// return b instanceof A;
}
// 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;
Grâce au JIT, le code est optimisé à un moment donné et nous obtenons:
- instanceof: 6ms
- isInstance: 12ms
- isAssignableFrom: 15ms
Remarque
À l'origine, ce post faisait son propre benchmark en utilisant une boucle for dans JAVA brut, ce qui a donné des résultats peu fiables car une optimisation comme Just In Time peut éliminer la boucle. Il s'agissait donc principalement de mesurer le temps nécessaire au compilateur JIT pour optimiser la boucle: voir Test de performances indépendant du nombre d'itérations pour plus de détails
Questions connexes