静态块中的初始化 final 和 not final 静态字段
Initialization final and not final static fields in static block
Here 我发现以下代码显示了 MethodHandles 和 Reflection 的性能差异:
@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.Thread)
public class MHOpto {
private int value = 42;
private static final Field static_reflective;
private static final MethodHandle static_unreflect;
private static final MethodHandle static_mh;
private static Field reflective;
private static MethodHandle unreflect;
private static MethodHandle mh;
// We would normally use @Setup, but we need to initialize "static final" fields here...
static {
try {
reflective = MHOpto.class.getDeclaredField("value");
unreflect = MethodHandles.lookup().unreflectGetter(reflective);
mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
static_reflective = reflective;
static_unreflect = unreflect; //LINE X!!!
static_mh = mh;
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}
@Benchmark
public int plain() {
return value;
}
@Benchmark
public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) reflective.get(this);
}
@Benchmark
public int dynamic_unreflect_invoke() throws Throwable {
return (int) unreflect.invoke(this);
}
@Benchmark
public int dynamic_unreflect_invokeExact() throws Throwable {
return (int) unreflect.invokeExact(this);
}
@Benchmark
public int dynamic_mh_invoke() throws Throwable {
return (int) mh.invoke(this);
}
@Benchmark
public int dynamic_mh_invokeExact() throws Throwable {
return (int) mh.invokeExact(this);
}
@Benchmark
public int static_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) static_reflective.get(this);
}
@Benchmark
public int static_unreflect_invoke() throws Throwable {
return (int) static_unreflect.invoke(this);
}
@Benchmark
public int static_unreflect_invokeExact() throws Throwable {
return (int) static_unreflect.invokeExact(this);
}
@Benchmark
public int static_mh_invoke() throws Throwable {
return (int) static_mh.invoke(this);
}
@Benchmark
public int static_mh_invokeExact() throws Throwable {
return (int) static_mh.invokeExact(this);
}
}
结果如下:
Benchmark Mode Cnt Score Error Units
MHOpto.dynamic_mh_invoke avgt 25 4.393 ± 0.003 ns/op
MHOpto.dynamic_mh_invokeExact avgt 25 4.394 ± 0.007 ns/op
MHOpto.dynamic_reflect avgt 25 5.230 ± 0.020 ns/op
MHOpto.dynamic_unreflect_invoke avgt 25 4.404 ± 0.023 ns/op
MHOpto.dynamic_unreflect_invokeExact avgt 25 4.397 ± 0.014 ns/op
MHOpto.plain avgt 25 1.858 ± 0.002 ns/op
MHOpto.static_mh_invoke avgt 25 1.862 ± 0.015 ns/op
MHOpto.static_mh_invokeExact avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_reflect avgt 25 4.274 ± 0.011 ns/op
MHOpto.static_unreflect_invoke avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_unreflect_invokeExact avgt 25 1.858 ± 0.002 ns/op
我不明白的是这行代码:
static_unreflect = unreflect;
static_unreflect
(final)不等于unreflect
(非final)吗?那为什么他们在性能上表现出不同的结果呢?谁能解释一下?
只有 MethodHandle 的 static final
变体被 JIT 视为常量,参见例如ciField:
// Is this field a constant?
//
// Clarification: A field is considered constant if:
// 1. The field is both static and final
// 2. The field is not one of the special static/final
// non-constant fields. These are java.lang.System.in
// and java.lang.System.out. Abomination.
//
// A field is also considered constant if
// - it is marked @Stable and is non-null (or non-zero, if a primitive) or
// - it is trusted or
// - it is the target field of a CallSite object.
//
// See ciField::initialize_from() for more details.
//
// A user should also check the field value (constant_value().is_valid()), since
// constant fields of non-initialized classes don't have values yet.
bool is_constant() const { return _is_constant; }
并且只有通过 MethodHandles 的调用是内联的,请参阅 CallGenerator::for_method_handle_inline
它在其中进行了几次检查以查看接收者是否是常量,例如:
Node* receiver = kit.argument(0);
if (receiver->Opcode() == Op_ConP) {
...
} else {
print_inlining_failure(C, callee, jvms->depth() - 1, jvms->bci(),
"receiver not constant");
}
这种差异使得对 static final
MethodHandle 的调用可以内联,因此大致与普通情况一样快。
如果你打印出内联信息,你也可以看到这个。例如你可以添加类似:
@Fork(jvmArgsAppend="-Xlog:inlining*=trace:inlining-%p-static_mh_invokeExact.txt")
参考基准方法。
在静态情况下,您会看到内联调用:
@ 17 org.sample.MyBenchmark::static_mh_invokeExact (8 bytes) force inline by CompileCommand
@ 4 java.lang.invoke.LambdaForm$MH/0x00000008000f0040::invokeExact_MT (23 bytes) force inline by annotation
@ 10 java.lang.invoke.Invokers::checkExactType (17 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandle::type (5 bytes)
@ 14 java.lang.invoke.Invokers::checkCustomized (23 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
@ 19 java.lang.invoke.LambdaForm$MH/0x00000008000f0440::getInt (34 bytes) force inline by annotation
@ 7 java.lang.invoke.DirectMethodHandle::fieldOffset (9 bytes) force inline by annotation
@ 12 java.lang.invoke.DirectMethodHandle::checkBase (5 bytes) force inline by annotation
@ 1 java.util.Objects::requireNonNull (14 bytes)
@ 8 java.lang.NullPointerException::<init> (5 bytes) don't inline Throwable constructors
@ 30 jdk.internal.misc.Unsafe::getInt (0 bytes) intrinsic
我们一直内联到 Unsafe::getInt
调用(但重要的是我们看到 @ 19 java.lang.invoke.LambdaForm$MH/0x00000008000f0440::getInt
而不是 invokeBasic
)。
在动态情况下,您最多会看到:
@ 17 org.sample.MyBenchmark::dynamic_mh_invokeExact (8 bytes) force inline by CompileCommand
@ 4 java.lang.invoke.LambdaForm$MH/0x00000008000f0040::invokeExact_MT (23 bytes) force inline by annotation
@ 10 java.lang.invoke.Invokers::checkExactType (17 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandle::type (5 bytes)
@ 12 java.lang.invoke.Invokers::newWrongMethodTypeException (36 bytes) callee is too large
@ 14 java.lang.invoke.Invokers::checkCustomized (23 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
@ 19 java.lang.invoke.Invokers::maybeCustomize (28 bytes) don't inline by annotation
@ 19 java.lang.invoke.MethodHandle::invokeBasic(L)I (0 bytes) receiver not constant
即在这种情况下,仍然存在通过 invokeBasic
stub 的间接调用,因为 "receiver not constant".
Here 我发现以下代码显示了 MethodHandles 和 Reflection 的性能差异:
@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.Thread)
public class MHOpto {
private int value = 42;
private static final Field static_reflective;
private static final MethodHandle static_unreflect;
private static final MethodHandle static_mh;
private static Field reflective;
private static MethodHandle unreflect;
private static MethodHandle mh;
// We would normally use @Setup, but we need to initialize "static final" fields here...
static {
try {
reflective = MHOpto.class.getDeclaredField("value");
unreflect = MethodHandles.lookup().unreflectGetter(reflective);
mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
static_reflective = reflective;
static_unreflect = unreflect; //LINE X!!!
static_mh = mh;
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}
@Benchmark
public int plain() {
return value;
}
@Benchmark
public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) reflective.get(this);
}
@Benchmark
public int dynamic_unreflect_invoke() throws Throwable {
return (int) unreflect.invoke(this);
}
@Benchmark
public int dynamic_unreflect_invokeExact() throws Throwable {
return (int) unreflect.invokeExact(this);
}
@Benchmark
public int dynamic_mh_invoke() throws Throwable {
return (int) mh.invoke(this);
}
@Benchmark
public int dynamic_mh_invokeExact() throws Throwable {
return (int) mh.invokeExact(this);
}
@Benchmark
public int static_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) static_reflective.get(this);
}
@Benchmark
public int static_unreflect_invoke() throws Throwable {
return (int) static_unreflect.invoke(this);
}
@Benchmark
public int static_unreflect_invokeExact() throws Throwable {
return (int) static_unreflect.invokeExact(this);
}
@Benchmark
public int static_mh_invoke() throws Throwable {
return (int) static_mh.invoke(this);
}
@Benchmark
public int static_mh_invokeExact() throws Throwable {
return (int) static_mh.invokeExact(this);
}
}
结果如下:
Benchmark Mode Cnt Score Error Units
MHOpto.dynamic_mh_invoke avgt 25 4.393 ± 0.003 ns/op
MHOpto.dynamic_mh_invokeExact avgt 25 4.394 ± 0.007 ns/op
MHOpto.dynamic_reflect avgt 25 5.230 ± 0.020 ns/op
MHOpto.dynamic_unreflect_invoke avgt 25 4.404 ± 0.023 ns/op
MHOpto.dynamic_unreflect_invokeExact avgt 25 4.397 ± 0.014 ns/op
MHOpto.plain avgt 25 1.858 ± 0.002 ns/op
MHOpto.static_mh_invoke avgt 25 1.862 ± 0.015 ns/op
MHOpto.static_mh_invokeExact avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_reflect avgt 25 4.274 ± 0.011 ns/op
MHOpto.static_unreflect_invoke avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_unreflect_invokeExact avgt 25 1.858 ± 0.002 ns/op
我不明白的是这行代码:
static_unreflect = unreflect;
static_unreflect
(final)不等于unreflect
(非final)吗?那为什么他们在性能上表现出不同的结果呢?谁能解释一下?
只有 MethodHandle 的 static final
变体被 JIT 视为常量,参见例如ciField:
// Is this field a constant?
//
// Clarification: A field is considered constant if:
// 1. The field is both static and final
// 2. The field is not one of the special static/final
// non-constant fields. These are java.lang.System.in
// and java.lang.System.out. Abomination.
//
// A field is also considered constant if
// - it is marked @Stable and is non-null (or non-zero, if a primitive) or
// - it is trusted or
// - it is the target field of a CallSite object.
//
// See ciField::initialize_from() for more details.
//
// A user should also check the field value (constant_value().is_valid()), since
// constant fields of non-initialized classes don't have values yet.
bool is_constant() const { return _is_constant; }
并且只有通过 MethodHandles 的调用是内联的,请参阅 CallGenerator::for_method_handle_inline
它在其中进行了几次检查以查看接收者是否是常量,例如:
Node* receiver = kit.argument(0);
if (receiver->Opcode() == Op_ConP) {
...
} else {
print_inlining_failure(C, callee, jvms->depth() - 1, jvms->bci(),
"receiver not constant");
}
这种差异使得对 static final
MethodHandle 的调用可以内联,因此大致与普通情况一样快。
如果你打印出内联信息,你也可以看到这个。例如你可以添加类似:
@Fork(jvmArgsAppend="-Xlog:inlining*=trace:inlining-%p-static_mh_invokeExact.txt")
参考基准方法。
在静态情况下,您会看到内联调用:
@ 17 org.sample.MyBenchmark::static_mh_invokeExact (8 bytes) force inline by CompileCommand
@ 4 java.lang.invoke.LambdaForm$MH/0x00000008000f0040::invokeExact_MT (23 bytes) force inline by annotation
@ 10 java.lang.invoke.Invokers::checkExactType (17 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandle::type (5 bytes)
@ 14 java.lang.invoke.Invokers::checkCustomized (23 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
@ 19 java.lang.invoke.LambdaForm$MH/0x00000008000f0440::getInt (34 bytes) force inline by annotation
@ 7 java.lang.invoke.DirectMethodHandle::fieldOffset (9 bytes) force inline by annotation
@ 12 java.lang.invoke.DirectMethodHandle::checkBase (5 bytes) force inline by annotation
@ 1 java.util.Objects::requireNonNull (14 bytes)
@ 8 java.lang.NullPointerException::<init> (5 bytes) don't inline Throwable constructors
@ 30 jdk.internal.misc.Unsafe::getInt (0 bytes) intrinsic
我们一直内联到 Unsafe::getInt
调用(但重要的是我们看到 @ 19 java.lang.invoke.LambdaForm$MH/0x00000008000f0440::getInt
而不是 invokeBasic
)。
在动态情况下,您最多会看到:
@ 17 org.sample.MyBenchmark::dynamic_mh_invokeExact (8 bytes) force inline by CompileCommand
@ 4 java.lang.invoke.LambdaForm$MH/0x00000008000f0040::invokeExact_MT (23 bytes) force inline by annotation
@ 10 java.lang.invoke.Invokers::checkExactType (17 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandle::type (5 bytes)
@ 12 java.lang.invoke.Invokers::newWrongMethodTypeException (36 bytes) callee is too large
@ 14 java.lang.invoke.Invokers::checkCustomized (23 bytes) force inline by annotation
@ 1 java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
@ 19 java.lang.invoke.Invokers::maybeCustomize (28 bytes) don't inline by annotation
@ 19 java.lang.invoke.MethodHandle::invokeBasic(L)I (0 bytes) receiver not constant
即在这种情况下,仍然存在通过 invokeBasic
stub 的间接调用,因为 "receiver not constant".