Permalink
Browse files

The form 'expr rescue {simple}' where simple is immediate values or

values which do not cause any method execution (or side-effects) will
omit generating backtraces since there is no way to get access to $!.
This improves performance of these simple catch-all forms by ~48x.

Note: a follow-on commit can fix 'begin; expr; rescue; {simple}; end' later.
It is much less common and not as ripe a target.

BEFORE:

system ~/work/jruby master 814% jruby --dev ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil    89.000  i/100ms
moo rescue nil (single)
                        91.000  i/100ms
-------------------------------------------------
begin moo rescue nil    956.234  (± 4.4%) i/s -      9.612k
moo rescue nil (single)
                        955.879  (± 4.8%) i/s -      9.555k
system ~/work/jruby master 815% jruby -X-C ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil   100.000  i/100ms
moo rescue nil (single)
                       106.000  i/100ms
-------------------------------------------------
begin moo rescue nil      1.085k (± 5.2%) i/s -     10.900k
moo rescue nil (single)
                          1.072k (± 5.5%) i/s -     10.706k
system ~/work/jruby master 816% jruby ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil   104.000  i/100ms
moo rescue nil (single)
                       105.000  i/100ms
-------------------------------------------------
begin moo rescue nil      1.074k (± 5.7%) i/s -     10.712k
moo rescue nil (single)
                          1.089k (± 5.0%) i/s -     10.920k
system ~/work/jruby master 817% jruby -Xcompile.invokedynamic=true ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil   112.000  i/100ms
moo rescue nil (single)
                       122.000  i/100ms
-------------------------------------------------
begin moo rescue nil      1.275k (± 5.4%) i/s -     12.768k
moo rescue nil (single)
                          1.253k (± 4.7%) i/s -     12.566k

AFTER:

system ~/work/jruby master * 820% jruby --dev ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil    89.000  i/100ms
moo rescue nil (single)
                         1.199k i/100ms
-------------------------------------------------
begin moo rescue nil    921.217  (± 5.1%) i/s -      9.256k
moo rescue nil (single)
                         12.833k (± 4.9%) i/s -    128.293k
system ~/work/jruby master * 821% jruby -X-C ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil   100.000  i/100ms
moo rescue nil (single)
                         3.037k i/100ms
-------------------------------------------------
begin moo rescue nil      1.031k (± 6.2%) i/s -     10.300k
moo rescue nil (single)
                         35.393k (± 5.5%) i/s -    355.329k
system ~/work/jruby master * 822% jruby ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil   100.000  i/100ms
moo rescue nil (single)
                         4.725k i/100ms
-------------------------------------------------
begin moo rescue nil      1.119k (± 4.6%) i/s -     11.200k
moo rescue nil (single)
                         52.318k (± 5.8%) i/s -    524.475k
system ~/work/jruby master * 823% jruby -Xcompile.invokedynamic=true ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil   106.000  i/100ms
moo rescue nil (single)
                         5.076k i/100ms
-------------------------------------------------
begin moo rescue nil      1.176k (± 5.5%) i/s -     11.766k
moo rescue nil (single)
                         49.198k (± 5.5%) i/s -    492.372k

MRI 2.2.2:

system ~/work/jruby master * 824% mri22 ../snippets/bench2.rb
Calculating -------------------------------------
begin moo rescue nil     2.771k i/100ms
moo rescue nil (single)
                         2.707k i/100ms
-------------------------------------------------
begin moo rescue nil     28.586k (± 5.2%) i/s -    285.413k
moo rescue nil (single)
                         28.470k (± 4.2%) i/s -    284.235k
  • Loading branch information...
1 parent 55be66e commit fb4dcb4ee17a2b6bff8f6c7be8a334cc8b1c6d78 @enebo enebo committed Sep 28, 2015
Showing with 157 additions and 29 deletions.
  1. +1 −1 core/src/main/java/org/jruby/ast/BignumNode.java
  2. +1 −1 core/src/main/java/org/jruby/ast/ClassVarNode.java
  3. +1 −1 core/src/main/java/org/jruby/ast/ComplexNode.java
  4. +1 −1 core/src/main/java/org/jruby/ast/FalseNode.java
  5. +1 −1 core/src/main/java/org/jruby/ast/FileNode.java
  6. +1 −1 core/src/main/java/org/jruby/ast/FixnumNode.java
  7. +1 −1 core/src/main/java/org/jruby/ast/FloatNode.java
  8. +1 −1 core/src/main/java/org/jruby/ast/GlobalVarNode.java
  9. +1 −1 core/src/main/java/org/jruby/ast/InstVarNode.java
  10. +1 −1 core/src/main/java/org/jruby/ast/LocalVarNode.java
  11. +1 −1 core/src/main/java/org/jruby/ast/NilNode.java
  12. +1 −1 core/src/main/java/org/jruby/ast/RationalNode.java
  13. +12 −0 core/src/main/java/org/jruby/ast/RescueModNode.java
  14. +1 −1 core/src/main/java/org/jruby/ast/SelfNode.java
  15. +7 −0 core/src/main/java/org/jruby/ast/SideEffectFree.java
  16. +1 −1 core/src/main/java/org/jruby/ast/StrNode.java
  17. +1 −1 core/src/main/java/org/jruby/ast/SymbolNode.java
  18. +1 −1 core/src/main/java/org/jruby/ast/TrueNode.java
  19. +14 −11 core/src/main/java/org/jruby/exceptions/RaiseException.java
  20. +19 −0 core/src/main/java/org/jruby/ir/IRBuilder.java
  21. +7 −0 core/src/main/java/org/jruby/ir/IRManager.java
  22. +1 −0 core/src/main/java/org/jruby/ir/IRVisitor.java
  23. +2 −1 core/src/main/java/org/jruby/ir/Operation.java
  24. +59 −0 core/src/main/java/org/jruby/ir/instructions/ToggleBacktraceInstr.java
  25. +4 −0 core/src/main/java/org/jruby/ir/interpreter/InterpreterEngine.java
  26. +1 −0 core/src/main/java/org/jruby/ir/persistence/IRReaderStream.java
  27. +7 −0 core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
  28. +1 −1 core/src/main/java/org/jruby/parser/ParserSupport.java
  29. +7 −0 core/src/main/java/org/jruby/runtime/ThreadContext.java
@@ -41,7 +41,7 @@
/**
* Represents a big integer literal.
*/
-public class BignumNode extends NumericNode {
+public class BignumNode extends NumericNode implements SideEffectFree {
private BigInteger value;
public BignumNode(ISourcePosition position, BigInteger value) {
@@ -40,7 +40,7 @@
/**
* Access to a class variable.
*/
-public class ClassVarNode extends Node implements INameNode {
+public class ClassVarNode extends Node implements INameNode, SideEffectFree {
private String name;
public ClassVarNode(ISourcePosition position, String name) {
@@ -14,7 +14,7 @@
*
* @author enebo
*/
-public class ComplexNode extends NumericNode {
+public class ComplexNode extends NumericNode implements SideEffectFree {
private NumericNode y;
public ComplexNode(ISourcePosition position, NumericNode y) {
@@ -40,7 +40,7 @@
/**
* Represents a false literal.
*/
-public class FalseNode extends Node implements INameNode {
+public class FalseNode extends Node implements INameNode, SideEffectFree {
public FalseNode(ISourcePosition position) {
super(position, false);
}
@@ -34,7 +34,7 @@
/**
* Represents __FILE__ nodes
*/
-public class FileNode extends StrNode {
+public class FileNode extends StrNode implements SideEffectFree {
public FileNode(ISourcePosition position, ByteList value) {
super(position, value);
}
@@ -40,7 +40,7 @@
/**
* Represents an integer literal.
*/
-public class FixnumNode extends NumericNode implements ILiteralNode {
+public class FixnumNode extends NumericNode implements ILiteralNode, SideEffectFree {
private long value;
public FixnumNode(ISourcePosition position, long value) {
@@ -40,7 +40,7 @@
/**
* Represents a float literal.
*/
-public class FloatNode extends NumericNode implements ILiteralNode {
+public class FloatNode extends NumericNode implements ILiteralNode, SideEffectFree {
private double value;
public FloatNode(ISourcePosition position, double value) {
@@ -40,7 +40,7 @@
/**
* access to a global variable.
*/
-public class GlobalVarNode extends Node implements INameNode {
+public class GlobalVarNode extends Node implements INameNode, SideEffectFree {
private String name;
public GlobalVarNode(ISourcePosition position, String name) {
@@ -41,7 +41,7 @@
/**
* Represents an instance variable accessor.
*/
-public class InstVarNode extends Node implements INameNode {
+public class InstVarNode extends Node implements INameNode, SideEffectFree {
private String name;
public InstVarNode(ISourcePosition position, String name) {
@@ -40,7 +40,7 @@
/**
* Access a local variable
*/
-public class LocalVarNode extends Node implements INameNode, IScopedNode {
+public class LocalVarNode extends Node implements INameNode, IScopedNode, SideEffectFree {
// The name of the variable
private String name;
@@ -40,7 +40,7 @@
/**
* represents 'nil'
*/
-public class NilNode extends Node implements INameNode {
+public class NilNode extends Node implements INameNode, SideEffectFree {
public NilNode(ISourcePosition position) {
super(position, false);
}
@@ -14,7 +14,7 @@
*
* @author enebo
*/
-public class RationalNode extends NumericNode {
+public class RationalNode extends NumericNode implements SideEffectFree {
private final long numerator;
private final long denominator;
@@ -0,0 +1,12 @@
+package org.jruby.ast;
+
+import org.jruby.lexer.yacc.ISourcePosition;
+
+/**
+ * f rescue nil
+ */
+public class RescueModNode extends RescueNode {
+ public RescueModNode(ISourcePosition position, Node bodyNode, RescueBodyNode rescueNode) {
+ super(position, bodyNode, rescueNode, null /* else */);
+ }
+}
@@ -40,7 +40,7 @@
/**
* Represents 'self' keyword
*/
-public class SelfNode extends Node implements INameNode {
+public class SelfNode extends Node implements INameNode, SideEffectFree {
public SelfNode(ISourcePosition position) {
super(position, false);
}
@@ -0,0 +1,7 @@
+package org.jruby.ast;
+
+/**
+ * Created by enebo on 9/26/15.
+ */
+public interface SideEffectFree {
+}
@@ -42,7 +42,7 @@
/**
* Representing a simple String literal.
*/
-public class StrNode extends Node implements ILiteralNode {
+public class StrNode extends Node implements ILiteralNode, SideEffectFree {
private final ByteList value;
private final int codeRange;
@@ -54,7 +54,7 @@
/**
* Represents a symbol (:symbol_name).
*/
-public class SymbolNode extends Node implements ILiteralNode, INameNode {
+public class SymbolNode extends Node implements ILiteralNode, INameNode, SideEffectFree {
private String name;
private Encoding encoding;
@@ -39,7 +39,7 @@
/**
* Represents 'true'.
*/
-public class TrueNode extends Node implements INameNode {
+public class TrueNode extends Node implements INameNode, SideEffectFree {
public TrueNode(ISourcePosition position) {
super(position, false);
}
@@ -203,7 +203,9 @@ private void preRaise(ThreadContext context, StackTraceElement[] javaTrace) {
if (RubyInstanceConfig.LOG_EXCEPTIONS) TraceType.dumpException(exception);
- exception.prepareIntegratedBacktrace(context, javaTrace);
+ if (context.exceptionRequiresBacktrace) {
+ exception.prepareIntegratedBacktrace(context, javaTrace);
+ }
}
private void preRaise(ThreadContext context, IRubyObject backtrace) {
@@ -213,17 +215,18 @@ private void preRaise(ThreadContext context, IRubyObject backtrace) {
if (RubyInstanceConfig.LOG_EXCEPTIONS) TraceType.dumpException(exception);
- if (backtrace == null) {
- exception.prepareBacktrace(context, nativeException);
- } else {
- exception.forceBacktrace(backtrace);
- }
-
- // call Throwable.setStackTrace so that when RaiseException appears nested inside another exception,
- // Ruby stack trace gets displayed
+ // We can only omit backtraces of descendents of Standard error for 'foo rescue nil'
+ if (!exception.kind_of_p(context, context.runtime.getStandardError()).isTrue() ||
+ context.exceptionRequiresBacktrace) {
+ if (backtrace == null) {
+ exception.prepareBacktrace(context, nativeException);
+ } else {
+ exception.forceBacktrace(backtrace);
+ }
- // JRUBY-2673: if wrapping a NativeException, use the actual Java exception's trace as our Java trace
- if (context.exceptionRequiresBacktrace) {
+ // call Throwable.setStackTrace so that when RaiseException appears nested inside another exception,
+ // Ruby stack trace gets displayed
+ // JRUBY-2673: if wrapping a NativeException, use the actual Java exception's trace as our Java trace
if (exception instanceof NativeException) {
setStackTrace(((NativeException) exception).getCause().getStackTrace());
} else {
@@ -3034,7 +3034,19 @@ public Operand buildRescue(RescueNode node) {
return buildEnsureInternal(node, null);
}
+ private boolean canBacktraceBeRemoved(RescueNode rescueNode) {
+ // For now we will only contemplate 'foo rescue nil' cases but simple non-mod rescue forms can be added later.
+ if (!(rescueNode instanceof RescueModNode)) return false;
+
+ // FIXME: This MIGHT be able to expand to more complicated expressions like Hash or Array if they
+ // contain only SideEffectFree nodes. Constructing a literal out of these should be safe from
+ // effecting or being able to access $!.
+ return rescueNode.getRescueNode().getBodyNode() instanceof SideEffectFree;
+ }
+
private Operand buildRescueInternal(RescueNode rescueNode, EnsureBlockInfo ensure) {
+ boolean needsBacktrace = !canBacktraceBeRemoved(rescueNode);
+
// Labels marking start, else, end of the begin-rescue(-ensure)-end block
Label rBeginLabel = getNewLabel();
Label rEndLabel = ensure.end;
@@ -3045,6 +3057,7 @@ private Operand buildRescueInternal(RescueNode rescueNode, EnsureBlockInfo ensur
// Placeholder rescue instruction that tells rest of the compiler passes the boundaries of the rescue block.
addInstr(new ExceptionRegionStartMarkerInstr(rescueLabel));
activeRescuers.push(rescueLabel);
+ addInstr(manager.needsBacktrace(needsBacktrace));
// Body
Operand tmp = manager.getNil(); // default return value if for some strange reason, we neither have the body node or the else node!
@@ -3102,12 +3115,18 @@ private Operand buildRescueInternal(RescueNode rescueNode, EnsureBlockInfo ensur
// Start of rescue logic
addInstr(new LabelInstr(rescueLabel));
+ // This is optimized no backtrace path so we need to reenable backtraces since we are
+ // exiting that region.
+ if (!needsBacktrace) addInstr(manager.needsBacktrace(true));
+
// Save off exception & exception comparison type
Variable exc = addResultInstr(new ReceiveRubyExceptionInstr(createTemporaryVariable()));
// Build the actual rescue block(s)
buildRescueBodyInternal(rescueNode.getRescueNode(), rv, exc, rEndLabel);
+ if (!needsBacktrace) addInstr(manager.needsBacktrace(true));
+
activeRescueBlockStack.pop();
return rv;
}
@@ -5,6 +5,7 @@
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.LineNumberInstr;
import org.jruby.ir.instructions.ReceiveSelfInstr;
+import org.jruby.ir.instructions.ToggleBacktraceInstr;
import org.jruby.ir.listeners.IRScopeListener;
import org.jruby.ir.listeners.InstructionsListener;
import org.jruby.ir.operands.*;
@@ -41,6 +42,8 @@
private final Nil nil = new Nil();
private final Boolean tru = new Boolean(true);
private final Boolean fals = new Boolean(false);
+ public final ToggleBacktraceInstr needsBacktrace = new ToggleBacktraceInstr(true);
+ public final ToggleBacktraceInstr needsNoBacktrace = new ToggleBacktraceInstr(false);
// Listeners for debugging and testing of IR
private Set<CompilerPassListener> passListeners = new HashSet<CompilerPassListener>();
@@ -92,6 +95,10 @@ public IRModuleBody getObject() {
return object;
}
+ public ToggleBacktraceInstr needsBacktrace(boolean needsIt) {
+ return needsIt ? needsBacktrace : needsNoBacktrace;
+ }
+
public CompilerPassScheduler schedulePasses() {
return schedulePasses(compilerPasses);
}
@@ -116,6 +116,7 @@ private void error(Object object) {
public void StoreLocalVarInstr(StoreLocalVarInstr storelocalvarinstr) { error(storelocalvarinstr); }
public void ThreadPollInstr(ThreadPollInstr threadpollinstr) { error(threadpollinstr); }
public void ThrowExceptionInstr(ThrowExceptionInstr throwexceptioninstr) { error(throwexceptioninstr); }
+ public void ToggleBacktraceInstr(ToggleBacktraceInstr instr) { error(instr); }
public void ToAryInstr(ToAryInstr toaryinstr) { error(toaryinstr); }
public void UndefMethodInstr(UndefMethodInstr undefmethodinstr) { error(undefmethodinstr); }
public void UnresolvedSuperInstr(UnresolvedSuperInstr unresolvedsuperinstr) { error(unresolvedsuperinstr); }
@@ -207,7 +207,8 @@
PUSH_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
PUSH_BINDING(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
POP_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
- POP_BINDING(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect);
+ POP_BINDING(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
+ TOGGLE_BACKTRACE(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect);
public final OpClass opClass;
private int flags;
@@ -0,0 +1,59 @@
+package org.jruby.ir.instructions;
+
+import org.jruby.ir.IRVisitor;
+import org.jruby.ir.Operation;
+import org.jruby.ir.persistence.IRReaderDecoder;
+import org.jruby.ir.persistence.IRWriterEncoder;
+import org.jruby.ir.transformations.inlining.CloneInfo;
+
+/**
+ * This instruction toggles a single per thread field which specifies whether an exception
+ * being thrown needs to generate backtrace info. At any point after toggling this to be
+ * false (no backtrace) you may encounter a nested exception which does require a backtrace.
+ * This nested exception will toggle back to needing an exception.
+ *
+ * In theory, we could restore this field as we unwind frames but largely this optimization
+ * only occurs in very simple scenarios.
+ *
+ * Also important to note this is only requesting for a backtrace or not. If you request
+ * no backtrace but the error is not a StandardError exception it will still be required
+ * to generate a backtrace.
+ */
+public class ToggleBacktraceInstr extends NoOperandInstr {
+ private final boolean requiresBacktrace;
+
+ public ToggleBacktraceInstr(boolean requiresBacktrace) {
+ super(Operation.TOGGLE_BACKTRACE);
+
+ this.requiresBacktrace = requiresBacktrace;
+ }
+
+ public boolean requiresBacktrace() {
+ return requiresBacktrace;
+ }
+
+ public String[] toStringNonOperandArgs() {
+ return new String[] { "" + requiresBacktrace };
+ }
+
+ @Override
+ public void encode(IRWriterEncoder e) {
+ super.encode(e);
+ e.encode(requiresBacktrace);
+ }
+
+ public static ToggleBacktraceInstr decode(IRReaderDecoder d) {
+ return new ToggleBacktraceInstr(d.decodeBoolean());
+ }
+
+
+ @Override
+ public void visit(IRVisitor visitor) {
+ visitor.ToggleBacktraceInstr(this);
+ }
+
+ @Override
+ public Instr clone(CloneInfo info) {
+ return this;
+ }
+}
@@ -24,6 +24,7 @@
import org.jruby.ir.instructions.ReturnBase;
import org.jruby.ir.instructions.RuntimeHelperCall;
import org.jruby.ir.instructions.SearchConstInstr;
+import org.jruby.ir.instructions.ToggleBacktraceInstr;
import org.jruby.ir.instructions.TraceInstr;
import org.jruby.ir.instructions.boxing.AluInstr;
import org.jruby.ir.instructions.boxing.BoxBooleanInstr;
@@ -359,6 +360,9 @@ protected static void processBookKeepingOp(ThreadContext context, Instr instr, O
case LINE_NUM:
context.setLine(((LineNumberInstr)instr).lineNumber);
break;
+ case TOGGLE_BACKTRACE:
+ context.setExceptionRequiresBacktrace(((ToggleBacktraceInstr) instr).requiresBacktrace());
+ break;
case TRACE: {
if (context.runtime.hasEventHooks()) {
TraceInstr trace = (TraceInstr) instr;
@@ -281,6 +281,7 @@ public Instr decodeInstr() {
case THREAD_POLL: return ThreadPollInstr.decode(this);
case THROW: return ThrowExceptionInstr.decode(this);
case TO_ARY: return ToAryInstr.decode(this);
+ case TOGGLE_BACKTRACE: return ToggleBacktraceInstr.decode(this);
case UNDEF_METHOD: return UndefMethodInstr.decode(this);
case UNRESOLVED_SUPER: return UnresolvedSuperInstr.decode(this);
case YIELD: return YieldInstr.decode(this);
@@ -1709,6 +1709,13 @@ public void RuntimeHelperCall(RuntimeHelperCall runtimehelpercall) {
}
@Override
+ public void ToggleBacktraceInstr(ToggleBacktraceInstr instr) {
+ jvmMethod().loadContext();
+ jvmAdapter().pushBoolean(instr.requiresBacktrace());
+ jvmAdapter().invokevirtual(p(ThreadContext.class), "setExceptionRequiresBacktrace", sig(void.class, boolean.class));
+ }
+
+ @Override
public void NonlocalReturnInstr(NonlocalReturnInstr returninstr) {
jvmMethod().loadContext();
jvmLoadLocal(DYNAMIC_SCOPE);
Oops, something went wrong.

0 comments on commit fb4dcb4

Please sign in to comment.