Skip to content

Commit 39ad671

Browse files
uschindlerjdconrad
authored andcommitted
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
1 parent 1b02793 commit 39ad671

File tree

2 files changed

+52
-23
lines changed

2 files changed

+52
-23
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static java.lang.invoke.MethodHandles.Lookup;
3939
import static org.elasticsearch.painless.Compiler.Loader;
4040
import static org.elasticsearch.painless.WriterConstants.CLASS_VERSION;
41+
import static org.elasticsearch.painless.WriterConstants.CTOR_METHOD_NAME;
4142
import static org.elasticsearch.painless.WriterConstants.DELEGATE_BOOTSTRAP_HANDLE;
4243
import static org.objectweb.asm.Opcodes.ACC_FINAL;
4344
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
@@ -89,9 +90,13 @@
8990
* public static final class $$Lambda0 implements Consumer {
9091
* private List arg$0;
9192
*
92-
* public $$Lambda0(List arg$0) {
93+
* private $$Lambda0(List arg$0) {
9394
* this.arg$0 = arg$0;
9495
* }
96+
*
97+
* public static Consumer create$lambda(List arg$0) {
98+
* return new $$Lambda0(arg$0);
99+
* }
95100
*
96101
* public void accept(Object val$0) {
97102
* Painless$Script.lambda$0(this.arg$0, val$0);
@@ -120,10 +125,17 @@
120125
* on whether or not there are captures. If there are no captures
121126
* the same instance of the generated lambda class will be
122127
* returned each time by the factory method as there are no
123-
* changing values other than the arguments. If there are
124-
* captures a new instance of the generated lambda class will
125-
* be returned each time with the captures passed into the
128+
* changing values other than the arguments, the lambda is a singleton.
129+
* If there are captures, a new instance of the generated lambda class
130+
* will be returned each time with the captures passed into the
126131
* factory method to be stored in the member fields.
132+
* Instead of calling the ctor, a static factory method is created
133+
* in the lambda class, because a method handle to the ctor directly
134+
* is (currently) preventing Hotspot optimizer from correctly doing
135+
* escape analysis. Escape analysis is important to optimize the
136+
* code in a way, that a new instance is not created on each lambda
137+
* invocation with captures, stressing garbage collector (thanks
138+
* to Rémi Forax for the explanation about this on Jaxcon 2017!).
127139
*/
128140
public final class LambdaBootstrap {
129141

@@ -153,6 +165,11 @@ private Capture(int count, Class<?> type) {
153165
*/
154166
private static final String DELEGATED_CTOR_WRAPPER_NAME = "delegate$ctor";
155167

168+
/**
169+
* This method name is used to generate the static factory for capturing lambdas.
170+
*/
171+
private static final String LAMBDA_FACTORY_METHOD_NAME = "create$lambda";
172+
156173
/**
157174
* Generates a lambda class for a lambda function/method reference
158175
* within a Painless script. Variables with the prefix interface are considered
@@ -198,7 +215,8 @@ public static CallSite lambdaBootstrap(
198215

199216
// Handles the special case where a method reference refers to a ctor (we need a static wrapper method):
200217
if (delegateInvokeType == H_NEWINVOKESPECIAL) {
201-
generateStaticCtorDelegator(cw, delegateClassType, delegateMethodName, delegateMethodType);
218+
assert CTOR_METHOD_NAME.equals(delegateMethodName);
219+
generateStaticCtorDelegator(cw, ACC_PRIVATE, DELEGATED_CTOR_WRAPPER_NAME, delegateClassType, delegateMethodType);
202220
// replace the delegate with our static wrapper:
203221
delegateMethodName = DELEGATED_CTOR_WRAPPER_NAME;
204222
delegateClassType = lambdaClassType;
@@ -281,16 +299,15 @@ private static void generateLambdaConstructor(
281299
MethodType factoryMethodType,
282300
Capture[] captures) {
283301

284-
String conName = "<init>";
285302
String conDesc = factoryMethodType.changeReturnType(void.class).toMethodDescriptorString();
286-
Method conMeth = new Method(conName, conDesc);
303+
Method conMeth = new Method(CTOR_METHOD_NAME, conDesc);
287304
Type baseConType = Type.getType(Object.class);
288-
Method baseConMeth = new Method(conName,
305+
Method baseConMeth = new Method(CTOR_METHOD_NAME,
289306
MethodType.methodType(void.class).toMethodDescriptorString());
290-
int modifiers = ACC_PUBLIC;
307+
int modifiers = (captures.length > 0) ? ACC_PRIVATE : ACC_PUBLIC;
291308

292309
GeneratorAdapter constructor = new GeneratorAdapter(modifiers, conMeth,
293-
cw.visitMethod(modifiers, conName, conDesc, null, null));
310+
cw.visitMethod(modifiers, CTOR_METHOD_NAME, conDesc, null, null));
294311
constructor.visitCode();
295312
constructor.loadThis();
296313
constructor.invokeConstructor(baseConType, baseConMeth);
@@ -304,21 +321,31 @@ private static void generateLambdaConstructor(
304321

305322
constructor.returnValue();
306323
constructor.endMethod();
324+
325+
// Add a factory method, if lambda takes captures.
326+
// @uschindler says: I talked with Rémi Forax about this. Technically, a plain ctor
327+
// and a MethodHandle to the ctor would be enough - BUT: Hotspot is unable to
328+
// do escape analysis through a MethodHandles.findConstructor generated handle.
329+
// Because of this we create a factory method. With this factory method, the
330+
// escape analysis can figure out that everything is final and we don't need
331+
// an instance, so it can omit object creation on heap!
332+
if (captures.length > 0) {
333+
generateStaticCtorDelegator(cw, ACC_PUBLIC, LAMBDA_FACTORY_METHOD_NAME, lambdaClassType, factoryMethodType);
334+
}
307335
}
308336

309337
/**
310-
* Generates a factory method to delegate to constructors using
311-
* {@code INVOKEDYNAMIC} using the {@link #delegateBootstrap} type converter.
338+
* Generates a factory method to delegate to constructors.
312339
*/
313-
private static void generateStaticCtorDelegator(ClassWriter cw, Type delegateClassType, String delegateMethodName,
314-
MethodType delegateMethodType) {
315-
Method wrapperMethod = new Method(DELEGATED_CTOR_WRAPPER_NAME, delegateMethodType.toMethodDescriptorString());
340+
private static void generateStaticCtorDelegator(ClassWriter cw, int access, String delegatorMethodName,
341+
Type delegateClassType, MethodType delegateMethodType) {
342+
Method wrapperMethod = new Method(delegatorMethodName, delegateMethodType.toMethodDescriptorString());
316343
Method constructorMethod =
317-
new Method(delegateMethodName, delegateMethodType.changeReturnType(void.class).toMethodDescriptorString());
318-
int modifiers = ACC_PRIVATE | ACC_STATIC;
344+
new Method(CTOR_METHOD_NAME, delegateMethodType.changeReturnType(void.class).toMethodDescriptorString());
345+
int modifiers = access | ACC_STATIC;
319346

320347
GeneratorAdapter factory = new GeneratorAdapter(modifiers, wrapperMethod,
321-
cw.visitMethod(modifiers, DELEGATED_CTOR_WRAPPER_NAME, delegateMethodType.toMethodDescriptorString(), null, null));
348+
cw.visitMethod(modifiers, delegatorMethodName, delegateMethodType.toMethodDescriptorString(), null, null));
322349
factory.visitCode();
323350
factory.newInstance(delegateClassType);
324351
factory.dup();
@@ -329,7 +356,8 @@ private static void generateStaticCtorDelegator(ClassWriter cw, Type delegateCla
329356
}
330357

331358
/**
332-
* Generates the interface method that will delegate (call) to the delegate method.
359+
* Generates the interface method that will delegate (call) to the delegate method
360+
* with {@code INVOKEDYNAMIC} using the {@link #delegateBootstrap} type converter.
333361
*/
334362
private static void generateInterfaceMethod(
335363
ClassWriter cw,
@@ -464,8 +492,7 @@ private static CallSite createCaptureCallSite(
464492

465493
try {
466494
return new ConstantCallSite(
467-
lookup.findConstructor(lambdaClass, factoryMethodType.changeReturnType(void.class))
468-
.asType(factoryMethodType));
495+
lookup.findStatic(lambdaClass, LAMBDA_FACTORY_METHOD_NAME, factoryMethodType));
469496
} catch (ReflectiveOperationException exception) {
470497
throw new IllegalStateException("unable to create lambda class", exception);
471498
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ public final class WriterConstants {
5353

5454
public static final String CLASS_NAME = BASE_CLASS_NAME + "$Script";
5555
public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/'));
56+
57+
public static final String CTOR_METHOD_NAME = "<init>";
5658

57-
public static final Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", String.class, String.class, BitSet.class);
59+
public static final Method CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME, String.class, String.class, BitSet.class);
5860
public static final Method CLINIT = getAsmMethod(void.class, "<clinit>");
5961

6062
// All of these types are caught by the main method and rethrown as ScriptException
@@ -162,7 +164,7 @@ public final class WriterConstants {
162164
public static final Type STRING_TYPE = Type.getType(String.class);
163165
public static final Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
164166

165-
public static final Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class, "<init>");
167+
public static final Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME);
166168
public static final Method STRINGBUILDER_APPEND_BOOLEAN = getAsmMethod(StringBuilder.class, "append", boolean.class);
167169
public static final Method STRINGBUILDER_APPEND_CHAR = getAsmMethod(StringBuilder.class, "append", char.class);
168170
public static final Method STRINGBUILDER_APPEND_INT = getAsmMethod(StringBuilder.class, "append", int.class);

0 commit comments

Comments
 (0)