在 Function.andThen 中避免巨型调用站点
Avoiding megamorphic callsites in Function.andThen
正在查看Function.andThen
:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
由于此函数提供了巨大的价值,因此被广泛使用,这将其返回的函数变成了一个非常糟糕的超大型调用站点(在当前的 OpenJDK11 实现中)。即使通过 LamdaMetafactory
手动实例化 lambda(参见已接受的答案 ),所有生成的 lambda 共享同一个主体,因此具有相同的字节码索引(据我所知) 用于保留调用站点的类型配置文件数据,这使其再次变态。
是否有任何合理的方法来实现非巨型 andThen
without:
- 通过构建系统插件生成编译时代码
- J9、OpenJDK 热点和 Graal 不支持运行时代码生成
当然,最简单且资源消耗最少的解决方案是等到 JDK-8015416 得到修复。
作为变通方法,我们可能会生成新的不同 classes。
如果我们将方法限制在某些众所周知的操作上,例如通过 andThen
组合两个 Function
实例,为每个请求旋转一个新的 class 很容易实现,标准 API,无需执行字节码魔术:
public final class FunctionCombinator<T,U,R> implements Function<T,R> {
final Function<? super T,? extends U> first;
final Function<? super U,? extends R> second;
public FunctionCombinator(
Function<? super T,? extends U> f1, Function<? super U,? extends R> f2) {
first = f1;
second = f2;
}
@Override
public R apply(T t) {
return second.apply(first.apply(t));
}
@Override
public <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
return newCombinator(this, after);
}
@Override
public <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
return newCombinator(before, this);
}
@SuppressWarnings("unchecked")
public static <A,B,C> Function<A,C> newCombinator(
Function<? super A,? extends B> f1, Function<? super B,? extends C> f2) {
Objects.requireNonNull(f1);
Objects.requireNonNull(f2);
URL u = FunctionCombinator.class.getProtectionDomain()
.getCodeSource().getLocation();
try(URLClassLoader cl = new URLClassLoader(new URL[] { u }, null)) {
return cl.loadClass(FunctionCombinator.class.getName())
.asSubclass(Function.class)
.getConstructor(Function.class, Function.class)
.newInstance(f1, f2);
}
catch(IOException | ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
此代码只是在新的 class 加载器中重新读取 FunctionCombinator
的 class 定义,该加载器将 bootstrap 加载器作为其父加载器,这会阻止它解析到已经存在的 class.
与这种 class 加载程序相关的隐藏成本取决于实现。请注意,此代码在加载单个 class 后关闭加载程序以减少分配的资源。如果特定的 JVM 支持 class 卸载,函数组合器的加载器可能会在函数不再使用时被垃圾收集。
当然,静态编译的环境,不支持新增classes的环境是不支持的。对于那些环境,您无论如何都必须依赖静态代码分析的功能。您可以将此 class 生成设为可选,以避免在这些环境中出现故障:
static final boolean CREATE_NEW_CLASSES = Boolean.getBoolean("generateNewClassForAndThen");
@SuppressWarnings("unchecked")
public static <A,B,C> Function<A,C> newCombinator(
Function<? super A,? extends B> f1, Function<? super B,? extends C> f2) {
Objects.requireNonNull(f1);
Objects.requireNonNull(f2);
if(!CREATE_NEW_CLASSES) return new FunctionCombinator<>(f1, f2);
URL u = FunctionCombinator.class.getProtectionDomain().getCodeSource().getLocation();
try(URLClassLoader cl = new URLClassLoader(new URL[] { u }, null)) {
return cl.loadClass(FunctionCombinator.class.getName()).asSubclass(Function.class)
.getConstructor(Function.class, Function.class)
.newInstance(f1, f2);
}
catch(IOException | ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
然后,您需要在命令行中指定-DgenerateNewClassForAndThen=true
来激活此功能。
如果你想消耗更少的资源,你必须求助于Unsafe
…
任何对如何以更多不同方式应用 Holger 所提供答案的结果感兴趣的人,请查看 this class(它处于 WIP 状态)。
正在查看Function.andThen
:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
由于此函数提供了巨大的价值,因此被广泛使用,这将其返回的函数变成了一个非常糟糕的超大型调用站点(在当前的 OpenJDK11 实现中)。即使通过 LamdaMetafactory
手动实例化 lambda(参见已接受的答案
是否有任何合理的方法来实现非巨型 andThen
without:
- 通过构建系统插件生成编译时代码
- J9、OpenJDK 热点和 Graal 不支持运行时代码生成
当然,最简单且资源消耗最少的解决方案是等到 JDK-8015416 得到修复。
作为变通方法,我们可能会生成新的不同 classes。
如果我们将方法限制在某些众所周知的操作上,例如通过 andThen
组合两个 Function
实例,为每个请求旋转一个新的 class 很容易实现,标准 API,无需执行字节码魔术:
public final class FunctionCombinator<T,U,R> implements Function<T,R> {
final Function<? super T,? extends U> first;
final Function<? super U,? extends R> second;
public FunctionCombinator(
Function<? super T,? extends U> f1, Function<? super U,? extends R> f2) {
first = f1;
second = f2;
}
@Override
public R apply(T t) {
return second.apply(first.apply(t));
}
@Override
public <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
return newCombinator(this, after);
}
@Override
public <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
return newCombinator(before, this);
}
@SuppressWarnings("unchecked")
public static <A,B,C> Function<A,C> newCombinator(
Function<? super A,? extends B> f1, Function<? super B,? extends C> f2) {
Objects.requireNonNull(f1);
Objects.requireNonNull(f2);
URL u = FunctionCombinator.class.getProtectionDomain()
.getCodeSource().getLocation();
try(URLClassLoader cl = new URLClassLoader(new URL[] { u }, null)) {
return cl.loadClass(FunctionCombinator.class.getName())
.asSubclass(Function.class)
.getConstructor(Function.class, Function.class)
.newInstance(f1, f2);
}
catch(IOException | ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
此代码只是在新的 class 加载器中重新读取 FunctionCombinator
的 class 定义,该加载器将 bootstrap 加载器作为其父加载器,这会阻止它解析到已经存在的 class.
与这种 class 加载程序相关的隐藏成本取决于实现。请注意,此代码在加载单个 class 后关闭加载程序以减少分配的资源。如果特定的 JVM 支持 class 卸载,函数组合器的加载器可能会在函数不再使用时被垃圾收集。
当然,静态编译的环境,不支持新增classes的环境是不支持的。对于那些环境,您无论如何都必须依赖静态代码分析的功能。您可以将此 class 生成设为可选,以避免在这些环境中出现故障:
static final boolean CREATE_NEW_CLASSES = Boolean.getBoolean("generateNewClassForAndThen");
@SuppressWarnings("unchecked")
public static <A,B,C> Function<A,C> newCombinator(
Function<? super A,? extends B> f1, Function<? super B,? extends C> f2) {
Objects.requireNonNull(f1);
Objects.requireNonNull(f2);
if(!CREATE_NEW_CLASSES) return new FunctionCombinator<>(f1, f2);
URL u = FunctionCombinator.class.getProtectionDomain().getCodeSource().getLocation();
try(URLClassLoader cl = new URLClassLoader(new URL[] { u }, null)) {
return cl.loadClass(FunctionCombinator.class.getName()).asSubclass(Function.class)
.getConstructor(Function.class, Function.class)
.newInstance(f1, f2);
}
catch(IOException | ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
然后,您需要在命令行中指定-DgenerateNewClassForAndThen=true
来激活此功能。
如果你想消耗更少的资源,你必须求助于Unsafe
…
任何对如何以更多不同方式应用 Holger 所提供答案的结果感兴趣的人,请查看 this class(它处于 WIP 状态)。