/* * Copyright (c) 2014, Oracle America, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Oracle nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ package org.openjdk; import org.openjdk.jmh.annotations.*; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.concurrent.TimeUnit; @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) public class UnsafeImplicitNullCheck { static final long OFF; static final Unsafe U; static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); U = (Unsafe) field.get(null); OFF = U.objectFieldOffset(Target.class.getDeclaredField("f")); } catch (Exception e) { throw new AssertionError(e); } } Target t; @Setup public void setup() { t = new Target(); } @Benchmark public int plain() { return t.f; } @Benchmark public int plain_checked() { if (t == null) { throw new NullPointerException(); } return t.f; } @Benchmark public int plain_checked_local() { Target v = this.t; if (v == null) { throw new NullPointerException(); } return v.f; } @Benchmark public int unsafe_checked() { if (t == null) { throw new NullPointerException(); } return U.getIntVolatile(t, OFF); } @Benchmark public int unsafe_checked_local() { Target v = this.t; if (v == null) { throw new NullPointerException(); } return U.getIntVolatile(v, OFF); } private static class Target { public volatile int f; } /* jdk9/dev, 2015-12-04, Linux x86_64 release; Intel i7-4790K. All plain_* tests are compiled to the same: 0.60% 0.50% ↗ 0x00007f181d20b6a0: mov 0x10(%rsp),%r10 0.13% 0.16% │ 0x00007f181d20b6a5: mov 0xc(%r10),%r10d ; get field $t 10.48% 10.64% │ 0x00007f181d20b6a9: mov 0xc(%r12,%r10,8),%edx ; getfield $t.f + IMPLICIT NULL POINTER CHECK 6.93% 7.95% │ 0x00007f181d20b6ae: mov 0x18(%rsp),%rsi 0.20% 0.17% │ 0x00007f181d20b6b3: callq 0x00007f181d046020 ; call Blackhole.consume 15.85% 15.86% │ 0x00007f181d20b6b8: mov (%rsp),%r10 0.95% 0.99% │ 0x00007f181d20b6bc: movzbl 0x94(%r10),%r10d ; get field $isDone 0.33% 0.37% │ 0x00007f181d20b6c4: add $0x1,%rbp ; ops++ 10.88% 11.42% │ 0x00007f181d20b6c8: test %eax,0x182fc932(%rip) ; safepoint poll 0.73% 0.47% │ 0x00007f181d20b6ce: test %r10d,%r10d ; while (!isDone) ╰ 0x00007f181d20b6d1: je 0x00007f181d20b6a0 All unsafe_* tests compile to this, note the non-folded null-pointer check: 0.09% 0.13% ↗ 0x00007f3a8120f022: mov 0x10(%rsp),%r10 1.47% 0.94% │ 0x00007f3a8120f027: mov 0xc(%r10),%r10d ; get field t 9.99% 9.95% │ 0x00007f3a8120f02b: test %r10d,%r10d ; EXPLICIT null pointer check for $t │ 0x00007f3a8120f02e: je 0x00007f3a8120f085 0.06% │ 0x00007f3a8120f030: mov 0xc(%r12,%r10,8),%edx ; getIntVolatile, $t.f 6.99% 8.37% │ 0x00007f3a8120f035: mov 0x18(%rsp),%rsi 0.79% 0.56% │ 0x00007f3a8120f03a: nop 5.11% 5.55% │ 0x00007f3a8120f03b: callq 0x00007f3a81046020 ; call Blackhole.consume 13.25% 14.02% │ 0x00007f3a8120f040: mov (%rsp),%r10 0.13% 0.09% │ 0x00007f3a8120f044: movzbl 0x94(%r10),%r10d ; get field $isDone 1.52% 1.75% │ 0x00007f3a8120f04c: add $0x1,%rbp ; ops++ 9.91% 8.87% │ 0x00007f3a8120f050: test %eax,0x16197faa(%rip) ; safepoint poll 0.01% 0.01% │ 0x00007f3a8120f056: test %r10d,%r10d ; while (!isDone) ╰ 0x00007f3a8120f059: je 0x00007f3a8120f022 In Unsafe cases, we don't subsume the null-pointer check to the field access, even though the actual access instructions are the same (i.e. we know the offsets statically). */ }