Skip to content

Commit df56604

Browse files
committed
First pass attempting to optimize startup time
This patch is a bunch of hacky fixes as a proof of concept attempt to improve startup time. * This fixes the interpreter to interpret IR instructions that the IR builder generates without having to build a CFG or run any passes on them. There is no cloning of in instructions either. * If the scope runs at least N times (N = 20 right now), the CFG is then built, some basic passes are run and future runs will use the new optimized scope. In interpret mode (-X-C), this patch: * reduces startup time for gem list by 20+%. * reduces total user time by ~13% across a bunch of benchmarks. * reduces wall clock time by ~5% across the same benchmarks. * improves peak perf on some of those benchmarks and degrades peak perf on some. In default mode (-X-C), this patch: * reduces total user time by ~20% across a bunch of benchmarks. * reduces wall clock time by ~11% across the same benchmarks. This was passing all tests in an earlier version of this patch before I broke something messing and tweaking something. It is late at night, so pushing this as is. This branch is not ready for merge. It requires: * code cleanup * get rid of hacks * more testing * fixing broken tests
1 parent f3c4b71 commit df56604

12 files changed

+244
-41
lines changed

core/src/main/java/org/jruby/internal/runtime/methods/InterpretedIRBodyMethod.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
3636
if (IRRuntimeHelpers.isDebug()) doDebug();
3737

3838
InterpreterContext ic = ensureInstrsReady();
39-
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
39+
if (ic.hasExplicitCallProtocol()) {
40+
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
41+
} else {
42+
try {
43+
this.pre(ic, context, self, name, block, getImplementationClass());
44+
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
45+
} finally {
46+
this.post(ic, context);
47+
}
48+
}
4049
}
4150

4251
@Override

core/src/main/java/org/jruby/internal/runtime/methods/InterpretedIRMethod.java

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.jruby.parser.StaticScope;
1515
import org.jruby.runtime.Arity;
1616
import org.jruby.runtime.Block;
17+
import org.jruby.runtime.DynamicScope;
1718
import org.jruby.runtime.PositionAware;
1819
import org.jruby.runtime.ThreadContext;
1920
import org.jruby.runtime.Visibility;
@@ -83,11 +84,35 @@ public Arity getArity() {
8384
return this.arity;
8485
}
8586

87+
protected void post(InterpreterContext ic, ThreadContext context) {
88+
// update call stacks (pop: ..)
89+
context.popFrame();
90+
if (ic.popDynScope()) {
91+
context.popScope();
92+
}
93+
}
94+
95+
protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, RubyModule implClass) {
96+
// update call stacks (push: frame, class, scope, etc.)
97+
context.preMethodFrameOnly(implClass, name, self, block);
98+
if (ic.pushNewDynScope()) {
99+
context.pushScope(DynamicScope.newDynamicScope(ic.getStaticScope()));
100+
}
101+
context.setCurrentVisibility(getVisibility());
102+
}
103+
86104
public InterpreterContext ensureInstrsReady() {
87105
// Try unsync access first before calling more expensive method for getting IC
88106
InterpreterContext ic = method.getInterpreterContext();
89107

90-
return ic == null ? method.prepareForInterpretation() : ic;
108+
// Build/rebuild if necessary
109+
if (ic == null) {
110+
ic = method.prepareForInterpretation();
111+
} else if (ic.needsRebuilding()) {
112+
ic = method.prepareForInterpretation(true);
113+
}
114+
115+
return ic;
91116
}
92117

93118
@Override
@@ -105,12 +130,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
105130
}
106131
}
107132

108-
private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
133+
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
109134
IRubyObject self, String name, IRubyObject[] args, Block block) {
110135
try {
111136
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());
112137

113-
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
138+
if (ic.hasExplicitCallProtocol()) {
139+
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
140+
} else {
141+
try {
142+
this.pre(ic, context, self, name, block, implClass);
143+
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
144+
} finally {
145+
this.post(ic, context);
146+
}
147+
}
114148
} finally {
115149
ThreadContext.popBacktrace(context);
116150
}
@@ -131,12 +165,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
131165
}
132166
}
133167

134-
private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
168+
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
135169
IRubyObject self, String name, Block block) {
136170
try {
137171
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());
138172

139-
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
173+
if (ic.hasExplicitCallProtocol()) {
174+
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
175+
} else {
176+
try {
177+
this.pre(ic, context, self, name, block, implClass);
178+
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
179+
} finally {
180+
this.post(ic, context);
181+
}
182+
}
140183
} finally {
141184
ThreadContext.popBacktrace(context);
142185
}
@@ -157,12 +200,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
157200
}
158201
}
159202

160-
private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
203+
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
161204
IRubyObject self, String name, IRubyObject arg1, Block block) {
162205
try {
163206
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());
164207

165-
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
208+
if (ic.hasExplicitCallProtocol()) {
209+
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
210+
} else {
211+
try {
212+
this.pre(ic, context, self, name, block, implClass);
213+
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
214+
} finally {
215+
this.post(ic, context);
216+
}
217+
}
166218
} finally {
167219
ThreadContext.popBacktrace(context);
168220
}
@@ -183,12 +235,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
183235
}
184236
}
185237

186-
private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
238+
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
187239
IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) {
188240
try {
189241
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());
190242

191-
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
243+
if (ic.hasExplicitCallProtocol()) {
244+
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
245+
} else {
246+
try {
247+
this.pre(ic, context, self, name, block, implClass);
248+
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
249+
} finally {
250+
this.post(ic, context);
251+
}
252+
}
192253
} finally {
193254
ThreadContext.popBacktrace(context);
194255
}
@@ -209,12 +270,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
209270
}
210271
}
211272

212-
private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
273+
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
213274
IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) {
214275
try {
215276
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());
216277

217-
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
278+
if (ic.hasExplicitCallProtocol()) {
279+
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
280+
} else {
281+
try {
282+
this.pre(ic, context, self, name, block, implClass);
283+
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
284+
} finally {
285+
this.post(ic, context);
286+
}
287+
}
218288
} finally {
219289
ThreadContext.popBacktrace(context);
220290
}

core/src/main/java/org/jruby/ir/IRClosure.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, Stati
106106
}
107107

108108
@Override
109-
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
110-
return new ClosureInterpreterContext(this, instructionList);
109+
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
110+
return new ClosureInterpreterContext(this, instructionList, rebuild);
111111
}
112112

113113
public void setBeginEndBlock() {

core/src/main/java/org/jruby/ir/IREvalScript.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ public IREvalScript(IRManager manager, IRScope lexicalParent, String fileName,
3535
}
3636

3737
@Override
38-
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
39-
return new BeginEndInterpreterContext(this, instructionList);
38+
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
39+
return new BeginEndInterpreterContext(this, instructionList, rebuild);
4040
}
4141

4242
@Override

core/src/main/java/org/jruby/ir/IRScope.java

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
* and so on ...
5959
*/
6060
public abstract class IRScope implements ParseResult {
61+
6162
private static final Logger LOG = LoggerFactory.getLogger("IRScope");
6263

6364
private static AtomicInteger globalScopeCount = new AtomicInteger();
@@ -139,6 +140,13 @@ public abstract class IRScope implements ParseResult {
139140

140141
private TemporaryVariable yieldClosureVariable;
141142

143+
// What state is this scope in?
144+
enum ScopeState {
145+
INIT, INSTRS_CLONED, CFG_BUILT
146+
};
147+
148+
private ScopeState state = ScopeState.INIT;
149+
142150
// Used by cloning code
143151
protected IRScope(IRScope s, IRScope lexicalParent) {
144152
this.lexicalParent = lexicalParent;
@@ -163,6 +171,7 @@ protected IRScope(IRScope s, IRScope lexicalParent) {
163171

164172
this.localVars = new HashMap<String, LocalVariable>(s.localVars);
165173
this.scopeId = globalScopeCount.getAndIncrement();
174+
this.state = ScopeState.INIT; // SSS FIXME: Is this correct?
166175

167176
this.executedPasses = new ArrayList<CompilerPass>();
168177

@@ -208,6 +217,7 @@ public IRScope(IRManager manager, IRScope lexicalParent, String name,
208217

209218
this.localVars = new HashMap<String, LocalVariable>();
210219
this.scopeId = globalScopeCount.getAndIncrement();
220+
this.state = ScopeState.INIT;
211221

212222
this.executedPasses = new ArrayList<CompilerPass>();
213223

@@ -451,6 +461,7 @@ public CFG buildCFG() {
451461
this.instrList = null;
452462

453463
setCFG(newCFG);
464+
this.state = ScopeState.CFG_BUILT;
454465

455466
return newCFG;
456467
}
@@ -465,6 +476,20 @@ public CFG getCFG() {
465476

466477
@Interp
467478
protected Instr[] prepareInstructions() {
479+
if (getCFG() == null) {
480+
int n = instrList.size();
481+
Instr[] linearizedInstrArray = instrList.toArray(new Instr[n]);
482+
for (int ipc = 0; ipc < n; ipc++) {
483+
Instr i = linearizedInstrArray[ipc];
484+
i.setIPC(ipc);
485+
if (i instanceof LabelInstr) {
486+
((LabelInstr)i).getLabel().setTargetPC(ipc+1);
487+
}
488+
}
489+
490+
return linearizedInstrArray;
491+
}
492+
468493
setupLinearization();
469494

470495
boolean simple_method = this instanceof IRMethod;
@@ -603,26 +628,52 @@ protected void initScope(boolean jitMode) {
603628
}
604629

605630
/** Make version specific to scope which needs it (e.g. Closure vs non-closure). */
606-
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
607-
return new InterpreterContext(this, instructionList);
631+
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
632+
return new InterpreterContext(this, instructionList, rebuild);
633+
}
634+
635+
public InterpreterContext prepareForInterpretation() {
636+
return prepareForInterpretation(false);
637+
}
638+
639+
protected void cloneInstrs() {
640+
if (this.state == ScopeState.INIT) {
641+
// Clone instrs before modifying them
642+
SimpleCloneInfo cloneInfo = new SimpleCloneInfo(this, false);
643+
List<Instr> newInstrList = new ArrayList<Instr>(this.instrList.size());
644+
for (Instr instr: this.instrList) {
645+
newInstrList.add(instr.clone(cloneInfo));
646+
}
647+
this.instrList = newInstrList;
648+
this.state = ScopeState.INSTRS_CLONED;
649+
}
608650
}
609651

610652
/** Run any necessary passes to get the IR ready for interpretation */
611-
public synchronized InterpreterContext prepareForInterpretation() {
612-
if (interpreterContext != null) return interpreterContext; // Already prepared
653+
public synchronized InterpreterContext prepareForInterpretation(boolean rebuild) {
654+
if (interpreterContext != null) {
655+
if (!rebuild || getCFG() != null) {
656+
return interpreterContext; // Already prepared/rebuilt
657+
}
613658

614-
initScope(false);
659+
// If rebuilding, clone instrs before building cfg, running passes, etc.
660+
this.cloneInstrs();
661+
for (IRClosure cl: getClosures()) {
662+
cl.cloneInstrs();
663+
}
615664

616-
// System.out.println("-- passes run for: " + this + " = " + java.util.Arrays.toString(executedPasses.toArray()));
665+
// Build CFG, run passes, etc.
666+
initScope(false);
617667

618-
// Always add call protocol instructions now for both interpreter and JIT
619-
// since we are removing support for implicit stuff in the interpreter.
620-
// When JIT later runs this same pass, it will be a NOP there.
621-
if (!isUnsafeScope()) {
622-
(new AddCallProtocolInstructions()).run(this);
668+
// Always add call protocol instructions now for both interpreter and JIT
669+
// since we are removing support for implicit stuff in the interpreter.
670+
// When JIT later runs this same pass, it will be a NOP there.
671+
if (!isUnsafeScope()) {
672+
(new AddCallProtocolInstructions()).run(this);
673+
}
623674
}
624675

625-
interpreterContext = allocateInterpreterContext(prepareInstructions());
676+
interpreterContext = allocateInterpreterContext(prepareInstructions(), rebuild);
626677

627678
return interpreterContext;
628679
}

core/src/main/java/org/jruby/ir/IRScriptBody.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public void setTopLevelBindingScope(DynamicScope tlbScope) {
3131
}
3232

3333
@Override
34-
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
35-
return new BeginEndInterpreterContext(this, instructionList);
34+
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
35+
return new BeginEndInterpreterContext(this, instructionList, rebuild);
3636
}
3737

3838
@Override

core/src/main/java/org/jruby/ir/interpreter/BeginEndInterpreterContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
public class BeginEndInterpreterContext extends InterpreterContext {
1515
private List<IRClosure> beginBlocks;
1616

17-
public BeginEndInterpreterContext(IRScope scope, Instr[] instructions) {
18-
super(scope, instructions);
17+
public BeginEndInterpreterContext(IRScope scope, Instr[] instructions, boolean rebuild) {
18+
super(scope, instructions, rebuild);
1919

2020
beginBlocks = scope.getBeginBlocks();
2121
}

core/src/main/java/org/jruby/ir/interpreter/ClosureInterpreterContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
* Interpreter knowledge needed to interpret a closure.
1010
*/
1111
public class ClosureInterpreterContext extends InterpreterContext {
12-
public ClosureInterpreterContext(IRClosure scope, Instr[] instructions) {
13-
super(scope, instructions);
12+
public ClosureInterpreterContext(IRClosure scope, Instr[] instructions, boolean rebuild) {
13+
super(scope, instructions, rebuild);
1414
}
1515

1616
/**

core/src/main/java/org/jruby/ir/interpreter/Interpreter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.jruby.ir.IRTranslator;
1818
import org.jruby.ir.operands.IRException;
1919
import org.jruby.ir.operands.WrappedIRClosure;
20+
import org.jruby.ir.representations.CFG;
2021
import org.jruby.ir.runtime.IRBreakJump;
2122
import org.jruby.ir.runtime.IRRuntimeHelpers;
2223
import org.jruby.parser.StaticScope;

0 commit comments

Comments
 (0)