I was looking through new intrinsic methods in Java 9 and found that now the method Class.cast is intrinsic too along with Class.isInstance.
I did simple benchmark to check that:
@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, batchSize = 1000)
@Measurement(iterations = 20, time = 1, batchSize = 1000)
public class Casting {
public Object msg;
public Class<String> type;
@Setup
public void setup(BenchmarkParams params) {
type = String.class;
msg = "123";
}
@Benchmark
public boolean isInstanceMethod() {
return type.isInstance(msg);
}
@Benchmark
public boolean isInstanceMethodExplicit() {
return String.class.isInstance(msg);
}
@Benchmark
public boolean isInstanceRegular() {
return msg instanceof String;
}
@Benchmark
public String castMethod() {
return type.cast(msg);
}
@Benchmark
public String castMethodExplicit() {
return String.class.cast(msg);
}
@Benchmark
public String castRegular() {
return (String) msg;
}
}
Results:
Benchmark Mode Cnt Score Error Units
Casting.castMethod thrpt 20 254604.793 ± 3863.859 ops/s
Casting.castMethodExplicit thrpt 20 336046.787 ± 10059.986 ops/s
Casting.castRegular thrpt 20 344369.229 ± 4855.492 ops/s
Casting.isInstanceMethod thrpt 20 325867.697 ± 6511.731 ops/s
Casting.isInstanceMethodExplicit thrpt 20 415387.363 ± 2993.788 ops/s
Casting.isInstanceRegular thrpt 20 396613.371 ± 16799.378 ops/s
Env:
# JMH version: 1.19
# VM version: JDK 9, VM 9+181
# VM invoker: /usr/lib/jvm/java-9-oracle/bin/java
The result seems a bit unexpected. Both Class.cast and Class.isInstance are slower when invoking them via class variable. My understanding was that intrinsic should make both this method as fast as alternatives.
Does variable breaks intrinsic optimization? Or this overhead is expected?
There is no contradiction between the fact that methods are intrinsics and that they work slower for variables rather than for constants.
Consider the expressions Math.min(a, 2) and Math.min(a, b). Method Math.min is intrinsic in both cases. However, the first expression is obviously faster, since there is no need to load variable b, and the compiler can inline constant 2 directly in cmp instruction.
The same holds for isInstance/cast methods. When type is not a constant, JVM still needs to load the variable, check that it is non-null, and then load VM Klass pointer from java.lang.Class instance.
You can verify that intrinsics work by explicitly disabling them. In this case the benchmark will be much slower.
@Benchmark
public String castMethod() {
return type.cast(msg);
}
@Benchmark
@Fork(jvmArgs = {
"-XX:+UnlockDiagnosticVMOptions",
"-XX:DisableIntrinsic=_Class_cast,_isInstance",
})
public String castMethodNoIntrinsic() {
return type.cast(msg);
}
Results:
Benchmark Mode Cnt Score Error Units
Casting.castMethod avgt 10 4,777 ± 0,065 ns/op
Casting.castMethodNoIntrinsic avgt 10 28,557 ± 0,124 ns/op
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With