【问题标题】:How to force a new instantiation of a lamda-definition如何强制 lamda 定义的新实例化
【发布时间】:2019-09-19 02:11:00
【问题描述】:

Java 规范保证给定的 lamda 定义,例如() -> "Hello World",被编译/转换为一个实现类(每个定义,不是每个“看起来”相同的出现)。

有什么办法可以强制 java-compiler/jvm 生成一个新的 lamda-definition 而不是共享一个通用的?我目前正在实现一个库,该库将多个函数部分编织到 BiFunction 中,由于 java-spec 提供的保证(编辑:我已更正:Java-Spec 不保证单个共享类 - 当前的参考实现是这样做的):

        public <In, Out, A> BiFunction<In, Out, Out> weave(
             Function<? super In, A> getter,
             BiConsumer<? super Out, ? super A> consumer
        ) {
            return (in, out) -> {
                consumer.accept(out, getter.apply(in));
                return out;
            };
        }

通过此代码生成的每个 lamda 共享相同的 lamda-definition,因此大多是不可内联/不可优化的。

【问题讨论】:

  • 如果没有运行时代码生成,您似乎没有运气。
  • 存在一个基本的误解。规范中没有任何内容可以保证这一点。规范允许产生相同的实现类,但实际上,它明确表示相同的lambda表达式可能导致不同的实现类。
  • “不同的 lambda 表达式产生的对象不必属于不同的类”表示允许不同的 lambda 表达式产生同一个类(这在今天的参考实现)。下一点说“评估产生的每个对象都不必属于同一个类(例如,捕获的局部变量可能是内联的)。”这正是您的场景。相同的 lambda 表达式可能被评估为不同类的实例(这在今天的参考实现中也不会发生)。
  • @talex 每个BiFunction 实例只捕获一个BiConsumer 实例,总是以相同的实现结束。如果您为每个 BiFunction 实例生成一个不同的类,这将是一个完美的单态行为。但我认为这不是必需的,因为 JVM 可能会通过多个调用级别将函数代码内联到不同的专用版本中。关键是这里抓到的BiConsumer实例。也许,指定-XX:+TrustFinalNonStaticFields 可以在这里有所作为。
  • @talex 是的,JIT 能够内联方法调用,不管这个方法的类是如何生成的。实际上,静态 lambda 表达式是使用相同的 LambdaMetafactory 实例化的。

标签: java jvm inlining


【解决方案1】:

在当前实现中,生成的类(甚至是非捕获 lambda 表达式的实例)的缓存是 invokedynamic 指令的一个属性,它将重用第一次执行时完成的引导结果。

托管在LambdaMetafactory 类中的引导方法本身将在每次调用时生成一个新类。因此,当您直接使用此工厂时,您将在当前实现下每次调用都会获得一个新类。

public <In, Out, A> BiFunction<In, Out, Out> weave(
     Function<? super In, A> getter,
     BiConsumer<? super Out, ? super A> consumer) {

    MethodHandles.Lookup l = MethodHandles.lookup();
    try {
        MethodHandle target = l.findStatic(l.lookupClass(), "weaveLambdaBody",
            MethodType.methodType(Object.class, Function.class, BiConsumer.class,
                Object.class, Object.class));
        MethodType t = target.type().dropParameterTypes(0, 2);
        return (BiFunction<In, Out, Out>)LambdaMetafactory.metafactory(l, "apply",
            target.type().dropParameterTypes(2, 4).changeReturnType(BiFunction.class),
            t, target, t) .getTarget().invokeExact(getter, consumer);
    }
    catch(RuntimeException | Error e) {
        throw e;
    }
    catch(Throwable t) {
        throw new IllegalStateException(t);
    }
}
private static <In, Out, A> Out weaveLambdaBody(
    Function<? super In, A> getter,
    BiConsumer<? super Out, ? super A> consumer,
    In in, Out out) {

    consumer.accept(out, getter.apply(in));
    return out;
}

首先,您必须将 lambda 主体脱糖为方法。捕获的值首先出现在其参数列表中,然后是功能接口类型的参数。 LambdaMetafactory 有关于其用法的详尽文档。

虽然我出于文档目的保留了类型参数,但应该清楚的是,通过这样的操作,您会失去编译时的安全性。

【讨论】:

  • 谢谢,这很好用(有一些调整,因为MethodHandles.lookup() 在单独实现枚举常量中的接口时不会返回正确的类)。我当前的测试套件表明内联适用于这种方法或我的问题中的方法(当有一个调用点时)。我还有一些其他奇怪的性能问题,现在似乎与内联无关。感谢您的帮助!
猜你喜欢
  • 2011-01-10
  • 2011-04-27
  • 2011-11-14
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多