Painless: Optimize instance creation in LambdaBootstrap (#24618)

Optimize instance creation in LambdaBootstrap to allow Hotspot's escape analysis, preventing us from creating many instances stressing GC
This commit is contained in:
Uwe Schindler 2017-05-11 18:10:27 +02:00 committed by Jack Conradson
parent d9cac191a2
commit f7c50f5f71
2 changed files with 52 additions and 23 deletions

View File

@ -38,6 +38,7 @@ import java.security.PrivilegedAction;
import static java.lang.invoke.MethodHandles.Lookup; import static java.lang.invoke.MethodHandles.Lookup;
import static org.elasticsearch.painless.Compiler.Loader; import static org.elasticsearch.painless.Compiler.Loader;
import static org.elasticsearch.painless.WriterConstants.CLASS_VERSION; import static org.elasticsearch.painless.WriterConstants.CLASS_VERSION;
import static org.elasticsearch.painless.WriterConstants.CTOR_METHOD_NAME;
import static org.elasticsearch.painless.WriterConstants.DELEGATE_BOOTSTRAP_HANDLE; import static org.elasticsearch.painless.WriterConstants.DELEGATE_BOOTSTRAP_HANDLE;
import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
@ -89,10 +90,14 @@ import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
* public static final class $$Lambda0 implements Consumer { * public static final class $$Lambda0 implements Consumer {
* private List arg$0; * private List arg$0;
* *
* public $$Lambda0(List arg$0) { * private $$Lambda0(List arg$0) {
* this.arg$0 = arg$0; * this.arg$0 = arg$0;
* } * }
* *
* public static Consumer create$lambda(List arg$0) {
* return new $$Lambda0(arg$0);
* }
*
* public void accept(Object val$0) { * public void accept(Object val$0) {
* Painless$Script.lambda$0(this.arg$0, val$0); * Painless$Script.lambda$0(this.arg$0, val$0);
* } * }
@ -120,10 +125,17 @@ import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
* on whether or not there are captures. If there are no captures * on whether or not there are captures. If there are no captures
* the same instance of the generated lambda class will be * the same instance of the generated lambda class will be
* returned each time by the factory method as there are no * returned each time by the factory method as there are no
* changing values other than the arguments. If there are * changing values other than the arguments, the lambda is a singleton.
* captures a new instance of the generated lambda class will * If there are captures, a new instance of the generated lambda class
* be returned each time with the captures passed into the * will be returned each time with the captures passed into the
* factory method to be stored in the member fields. * factory method to be stored in the member fields.
* Instead of calling the ctor, a static factory method is created
* in the lambda class, because a method handle to the ctor directly
* is (currently) preventing Hotspot optimizer from correctly doing
* escape analysis. Escape analysis is important to optimize the
* code in a way, that a new instance is not created on each lambda
* invocation with captures, stressing garbage collector (thanks
* to Rémi Forax for the explanation about this on Jaxcon 2017!).
*/ */
public final class LambdaBootstrap { public final class LambdaBootstrap {
@ -153,6 +165,11 @@ public final class LambdaBootstrap {
*/ */
private static final String DELEGATED_CTOR_WRAPPER_NAME = "delegate$ctor"; private static final String DELEGATED_CTOR_WRAPPER_NAME = "delegate$ctor";
/**
* This method name is used to generate the static factory for capturing lambdas.
*/
private static final String LAMBDA_FACTORY_METHOD_NAME = "create$lambda";
/** /**
* Generates a lambda class for a lambda function/method reference * Generates a lambda class for a lambda function/method reference
* within a Painless script. Variables with the prefix interface are considered * within a Painless script. Variables with the prefix interface are considered
@ -198,7 +215,8 @@ public final class LambdaBootstrap {
// Handles the special case where a method reference refers to a ctor (we need a static wrapper method): // Handles the special case where a method reference refers to a ctor (we need a static wrapper method):
if (delegateInvokeType == H_NEWINVOKESPECIAL) { if (delegateInvokeType == H_NEWINVOKESPECIAL) {
generateStaticCtorDelegator(cw, delegateClassType, delegateMethodName, delegateMethodType); assert CTOR_METHOD_NAME.equals(delegateMethodName);
generateStaticCtorDelegator(cw, ACC_PRIVATE, DELEGATED_CTOR_WRAPPER_NAME, delegateClassType, delegateMethodType);
// replace the delegate with our static wrapper: // replace the delegate with our static wrapper:
delegateMethodName = DELEGATED_CTOR_WRAPPER_NAME; delegateMethodName = DELEGATED_CTOR_WRAPPER_NAME;
delegateClassType = lambdaClassType; delegateClassType = lambdaClassType;
@ -281,16 +299,15 @@ public final class LambdaBootstrap {
MethodType factoryMethodType, MethodType factoryMethodType,
Capture[] captures) { Capture[] captures) {
String conName = "<init>";
String conDesc = factoryMethodType.changeReturnType(void.class).toMethodDescriptorString(); String conDesc = factoryMethodType.changeReturnType(void.class).toMethodDescriptorString();
Method conMeth = new Method(conName, conDesc); Method conMeth = new Method(CTOR_METHOD_NAME, conDesc);
Type baseConType = Type.getType(Object.class); Type baseConType = Type.getType(Object.class);
Method baseConMeth = new Method(conName, Method baseConMeth = new Method(CTOR_METHOD_NAME,
MethodType.methodType(void.class).toMethodDescriptorString()); MethodType.methodType(void.class).toMethodDescriptorString());
int modifiers = ACC_PUBLIC; int modifiers = (captures.length > 0) ? ACC_PRIVATE : ACC_PUBLIC;
GeneratorAdapter constructor = new GeneratorAdapter(modifiers, conMeth, GeneratorAdapter constructor = new GeneratorAdapter(modifiers, conMeth,
cw.visitMethod(modifiers, conName, conDesc, null, null)); cw.visitMethod(modifiers, CTOR_METHOD_NAME, conDesc, null, null));
constructor.visitCode(); constructor.visitCode();
constructor.loadThis(); constructor.loadThis();
constructor.invokeConstructor(baseConType, baseConMeth); constructor.invokeConstructor(baseConType, baseConMeth);
@ -304,21 +321,31 @@ public final class LambdaBootstrap {
constructor.returnValue(); constructor.returnValue();
constructor.endMethod(); constructor.endMethod();
// Add a factory method, if lambda takes captures.
// @uschindler says: I talked with Rémi Forax about this. Technically, a plain ctor
// and a MethodHandle to the ctor would be enough - BUT: Hotspot is unable to
// do escape analysis through a MethodHandles.findConstructor generated handle.
// Because of this we create a factory method. With this factory method, the
// escape analysis can figure out that everything is final and we don't need
// an instance, so it can omit object creation on heap!
if (captures.length > 0) {
generateStaticCtorDelegator(cw, ACC_PUBLIC, LAMBDA_FACTORY_METHOD_NAME, lambdaClassType, factoryMethodType);
}
} }
/** /**
* Generates a factory method to delegate to constructors using * Generates a factory method to delegate to constructors.
* {@code INVOKEDYNAMIC} using the {@link #delegateBootstrap} type converter.
*/ */
private static void generateStaticCtorDelegator(ClassWriter cw, Type delegateClassType, String delegateMethodName, private static void generateStaticCtorDelegator(ClassWriter cw, int access, String delegatorMethodName,
MethodType delegateMethodType) { Type delegateClassType, MethodType delegateMethodType) {
Method wrapperMethod = new Method(DELEGATED_CTOR_WRAPPER_NAME, delegateMethodType.toMethodDescriptorString()); Method wrapperMethod = new Method(delegatorMethodName, delegateMethodType.toMethodDescriptorString());
Method constructorMethod = Method constructorMethod =
new Method(delegateMethodName, delegateMethodType.changeReturnType(void.class).toMethodDescriptorString()); new Method(CTOR_METHOD_NAME, delegateMethodType.changeReturnType(void.class).toMethodDescriptorString());
int modifiers = ACC_PRIVATE | ACC_STATIC; int modifiers = access | ACC_STATIC;
GeneratorAdapter factory = new GeneratorAdapter(modifiers, wrapperMethod, GeneratorAdapter factory = new GeneratorAdapter(modifiers, wrapperMethod,
cw.visitMethod(modifiers, DELEGATED_CTOR_WRAPPER_NAME, delegateMethodType.toMethodDescriptorString(), null, null)); cw.visitMethod(modifiers, delegatorMethodName, delegateMethodType.toMethodDescriptorString(), null, null));
factory.visitCode(); factory.visitCode();
factory.newInstance(delegateClassType); factory.newInstance(delegateClassType);
factory.dup(); factory.dup();
@ -329,7 +356,8 @@ public final class LambdaBootstrap {
} }
/** /**
* Generates the interface method that will delegate (call) to the delegate method. * Generates the interface method that will delegate (call) to the delegate method
* with {@code INVOKEDYNAMIC} using the {@link #delegateBootstrap} type converter.
*/ */
private static void generateInterfaceMethod( private static void generateInterfaceMethod(
ClassWriter cw, ClassWriter cw,
@ -464,8 +492,7 @@ public final class LambdaBootstrap {
try { try {
return new ConstantCallSite( return new ConstantCallSite(
lookup.findConstructor(lambdaClass, factoryMethodType.changeReturnType(void.class)) lookup.findStatic(lambdaClass, LAMBDA_FACTORY_METHOD_NAME, factoryMethodType));
.asType(factoryMethodType));
} catch (ReflectiveOperationException exception) { } catch (ReflectiveOperationException exception) {
throw new IllegalStateException("unable to create lambda class", exception); throw new IllegalStateException("unable to create lambda class", exception);
} }

View File

@ -54,7 +54,9 @@ public final class WriterConstants {
public static final String CLASS_NAME = BASE_CLASS_NAME + "$Script"; public static final String CLASS_NAME = BASE_CLASS_NAME + "$Script";
public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/')); public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/'));
public static final Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", String.class, String.class, BitSet.class); public static final String CTOR_METHOD_NAME = "<init>";
public static final Method CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME, String.class, String.class, BitSet.class);
public static final Method CLINIT = getAsmMethod(void.class, "<clinit>"); public static final Method CLINIT = getAsmMethod(void.class, "<clinit>");
// All of these types are caught by the main method and rethrown as ScriptException // All of these types are caught by the main method and rethrown as ScriptException
@ -162,7 +164,7 @@ public final class WriterConstants {
public static final Type STRING_TYPE = Type.getType(String.class); public static final Type STRING_TYPE = Type.getType(String.class);
public static final Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class); public static final Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
public static final Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class, "<init>"); public static final Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME);
public static final Method STRINGBUILDER_APPEND_BOOLEAN = getAsmMethod(StringBuilder.class, "append", boolean.class); public static final Method STRINGBUILDER_APPEND_BOOLEAN = getAsmMethod(StringBuilder.class, "append", boolean.class);
public static final Method STRINGBUILDER_APPEND_CHAR = getAsmMethod(StringBuilder.class, "append", char.class); public static final Method STRINGBUILDER_APPEND_CHAR = getAsmMethod(StringBuilder.class, "append", char.class);
public static final Method STRINGBUILDER_APPEND_INT = getAsmMethod(StringBuilder.class, "append", int.class); public static final Method STRINGBUILDER_APPEND_INT = getAsmMethod(StringBuilder.class, "append", int.class);