Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
subbuss committed Feb 18, 2015
1 parent f3c4b71 commit df56604
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 41 deletions.
Expand Up @@ -36,7 +36,16 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
if (IRRuntimeHelpers.isDebug()) doDebug();

InterpreterContext ic = ensureInstrsReady();
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
} else {
try {
this.pre(ic, context, self, name, block, getImplementationClass());
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
} finally {
this.post(ic, context);
}
}
}

@Override
Expand Down
Expand Up @@ -14,6 +14,7 @@
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.PositionAware;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
Expand Down Expand Up @@ -83,11 +84,35 @@ public Arity getArity() {
return this.arity;
}

protected void post(InterpreterContext ic, ThreadContext context) {
// update call stacks (pop: ..)
context.popFrame();
if (ic.popDynScope()) {
context.popScope();
}
}

protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, RubyModule implClass) {
// update call stacks (push: frame, class, scope, etc.)
context.preMethodFrameOnly(implClass, name, self, block);
if (ic.pushNewDynScope()) {
context.pushScope(DynamicScope.newDynamicScope(ic.getStaticScope()));
}
context.setCurrentVisibility(getVisibility());
}

public InterpreterContext ensureInstrsReady() {
// Try unsync access first before calling more expensive method for getting IC
InterpreterContext ic = method.getInterpreterContext();

return ic == null ? method.prepareForInterpretation() : ic;
// Build/rebuild if necessary
if (ic == null) {
ic = method.prepareForInterpretation();
} else if (ic.needsRebuilding()) {
ic = method.prepareForInterpretation(true);
}

return ic;
}

@Override
Expand All @@ -105,12 +130,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject[] args, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -131,12 +165,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -157,12 +200,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject arg1, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -183,12 +235,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -209,12 +270,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/IRClosure.java
Expand Up @@ -106,8 +106,8 @@ public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, Stati
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new ClosureInterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new ClosureInterpreterContext(this, instructionList, rebuild);
}

public void setBeginEndBlock() {
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/IREvalScript.java
Expand Up @@ -35,8 +35,8 @@ public IREvalScript(IRManager manager, IRScope lexicalParent, String fileName,
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new BeginEndInterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new BeginEndInterpreterContext(this, instructionList, rebuild);
}

@Override
Expand Down
75 changes: 63 additions & 12 deletions core/src/main/java/org/jruby/ir/IRScope.java
Expand Up @@ -58,6 +58,7 @@
* and so on ...
*/
public abstract class IRScope implements ParseResult {

private static final Logger LOG = LoggerFactory.getLogger("IRScope");

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

private TemporaryVariable yieldClosureVariable;

// What state is this scope in?
enum ScopeState {
INIT, INSTRS_CLONED, CFG_BUILT
};

private ScopeState state = ScopeState.INIT;

// Used by cloning code
protected IRScope(IRScope s, IRScope lexicalParent) {
this.lexicalParent = lexicalParent;
Expand All @@ -163,6 +171,7 @@ protected IRScope(IRScope s, IRScope lexicalParent) {

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

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

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

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

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

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

setCFG(newCFG);
this.state = ScopeState.CFG_BUILT;

return newCFG;
}
Expand All @@ -465,6 +476,20 @@ public CFG getCFG() {

@Interp
protected Instr[] prepareInstructions() {
if (getCFG() == null) {
int n = instrList.size();
Instr[] linearizedInstrArray = instrList.toArray(new Instr[n]);
for (int ipc = 0; ipc < n; ipc++) {
Instr i = linearizedInstrArray[ipc];
i.setIPC(ipc);
if (i instanceof LabelInstr) {
((LabelInstr)i).getLabel().setTargetPC(ipc+1);
}
}

return linearizedInstrArray;
}

setupLinearization();

boolean simple_method = this instanceof IRMethod;
Expand Down Expand Up @@ -603,26 +628,52 @@ protected void initScope(boolean jitMode) {
}

/** Make version specific to scope which needs it (e.g. Closure vs non-closure). */
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new InterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new InterpreterContext(this, instructionList, rebuild);
}

public InterpreterContext prepareForInterpretation() {
return prepareForInterpretation(false);
}

protected void cloneInstrs() {
if (this.state == ScopeState.INIT) {
// Clone instrs before modifying them
SimpleCloneInfo cloneInfo = new SimpleCloneInfo(this, false);
List<Instr> newInstrList = new ArrayList<Instr>(this.instrList.size());
for (Instr instr: this.instrList) {
newInstrList.add(instr.clone(cloneInfo));
}
this.instrList = newInstrList;
this.state = ScopeState.INSTRS_CLONED;
}
}

/** Run any necessary passes to get the IR ready for interpretation */
public synchronized InterpreterContext prepareForInterpretation() {
if (interpreterContext != null) return interpreterContext; // Already prepared
public synchronized InterpreterContext prepareForInterpretation(boolean rebuild) {
if (interpreterContext != null) {
if (!rebuild || getCFG() != null) {
return interpreterContext; // Already prepared/rebuilt
}

initScope(false);
// If rebuilding, clone instrs before building cfg, running passes, etc.
this.cloneInstrs();
for (IRClosure cl: getClosures()) {
cl.cloneInstrs();
}

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

// Always add call protocol instructions now for both interpreter and JIT
// since we are removing support for implicit stuff in the interpreter.
// When JIT later runs this same pass, it will be a NOP there.
if (!isUnsafeScope()) {
(new AddCallProtocolInstructions()).run(this);
// Always add call protocol instructions now for both interpreter and JIT
// since we are removing support for implicit stuff in the interpreter.
// When JIT later runs this same pass, it will be a NOP there.
if (!isUnsafeScope()) {
(new AddCallProtocolInstructions()).run(this);
}
}

interpreterContext = allocateInterpreterContext(prepareInstructions());
interpreterContext = allocateInterpreterContext(prepareInstructions(), rebuild);

return interpreterContext;
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/IRScriptBody.java
Expand Up @@ -31,8 +31,8 @@ public void setTopLevelBindingScope(DynamicScope tlbScope) {
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new BeginEndInterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new BeginEndInterpreterContext(this, instructionList, rebuild);
}

@Override
Expand Down
Expand Up @@ -14,8 +14,8 @@
public class BeginEndInterpreterContext extends InterpreterContext {
private List<IRClosure> beginBlocks;

public BeginEndInterpreterContext(IRScope scope, Instr[] instructions) {
super(scope, instructions);
public BeginEndInterpreterContext(IRScope scope, Instr[] instructions, boolean rebuild) {
super(scope, instructions, rebuild);

beginBlocks = scope.getBeginBlocks();
}
Expand Down
Expand Up @@ -9,8 +9,8 @@
* Interpreter knowledge needed to interpret a closure.
*/
public class ClosureInterpreterContext extends InterpreterContext {
public ClosureInterpreterContext(IRClosure scope, Instr[] instructions) {
super(scope, instructions);
public ClosureInterpreterContext(IRClosure scope, Instr[] instructions, boolean rebuild) {
super(scope, instructions, rebuild);
}

/**
Expand Down
Expand Up @@ -17,6 +17,7 @@
import org.jruby.ir.IRTranslator;
import org.jruby.ir.operands.IRException;
import org.jruby.ir.operands.WrappedIRClosure;
import org.jruby.ir.representations.CFG;
import org.jruby.ir.runtime.IRBreakJump;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.parser.StaticScope;
Expand Down

0 comments on commit df56604

Please sign in to comment.