【问题标题】:Initialization final and not final static fields in static block初始化静态块中的最终和非最终静态字段
【发布时间】:2019-11-13 13:06:48
【问题描述】:

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(最终)不等于unreflect(非最终)吗?那为什么他们在性能上表现出不同的结果呢?谁能解释一下?

【问题讨论】:

  • 其实unreflect和static_unreflect是有区别的:后者定义为final。
  • @Turing85 谢谢。我看到。但是这两个变量只设置一次 - 当第一次加载类并执行静态块时。为什么重要?
  • 那么final 在这种情况下可能很重要。由于参考永远不会改变,因此可以对其进行优化。我不确定热点编译器是否能够推断出unreflect 引用实际上是最终的。如果不能,这可能有助于提高性能
  • 实测结果已经回答了这个问题……
  • @Holger 有一个来自 Shipilev 的精彩视频(不过是俄语)here。演讲中有好几次他提到当MethodHandles 是常量时,常量折叠最好发生

标签: java methodhandle


【解决方案1】:

只有 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 进行间接调用,因为“接收者不是恒定的”。

【讨论】:

  • 所以这是特定于 MethodHandles 的结果?
  • @Eugene 差不多。当使用例如相反,JIT 还可以进行类型分析(即检查我们实际看到的具体类型)并基于此进行推测内联,但 MethodHandles 没有等效项(至少目前还没有)。
  • @Eugene 您可以做的一个技巧是为每个 MethodHandle(例如使用 ASM)生成一个类,其中 MethodHandle 卡在常量池或 static final 字段中,并且该类有一个 getter方法句柄。在您基本上调用o.get().invokeExact(...) 的地方,JIT 将对类进行类型分析,对 getter 进行推测内联,此时 MethodHandle 再次成为常量,并且可以内联。
  • @这是否意味着在 MethodHandle Java 语言不按照规范工作的情况下?我的意思是static_unreflect(最终)必须给出与unreflect (非最终)相同的结果,但事实并非如此。根据规范,final 表明该变量是常量 - 我们无法在初始化后在代码中更改它,并且它一定不会影响性能。
  • @Pavel_K “它不能影响性能。”你有链接吗? MethodHandle 调用的结果是42,不管它的计算速度有多快,所以结果还是一样的。
猜你喜欢
  • 1970-01-01
  • 2014-10-06
  • 2013-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-19
  • 1970-01-01
相关资源
最近更新 更多