I've played a bit with wiring a non-polymorphic inline cache into the
DefaultCallAdapter. Here's what the numbers look like:
method_dispatch_only (before):
Test interpreted: 100k loops calling self's foo 100 times
2.626000 0.000000 2.626000 ( 2.626000)
3.792000 0.000000 3.792000 ( 3.792000)
1.919000 0.000000 1.919000 ( 1.919000)
1.841000 0.000000 1.841000 ( 1.841000)
1.818000 0.000000 1.818000 ( 1.817000)
1.862000 0.000000 1.862000 ( 1.863000)
1.879000 0.000000 1.879000 ( 1.880000)
1.944000 0.000000 1.944000 ( 1.944000)
1.808000 0.000000 1.808000 ( 1.808000)
1.805000 0.000000 1.805000 ( 1.805000)
method_dispatch_only (after):
Test interpreted: 100k loops calling self's foo 100 times
2.677000 0.000000 2.677000 ( 2.677000)
2.946000 0.000000 2.946000 ( 2.947000)
1.455000 0.000000 1.455000 ( 1.456000)
1.464000 0.000000 1.464000 ( 1.464000)
1.452000 0.000000 1.452000 ( 1.452000)
1.461000 0.000000 1.461000 ( 1.461000)
1.463000 0.000000 1.463000 ( 1.463000)
1.461000 0.000000 1.461000 ( 1.461000)
1.483000 0.000000 1.483000 ( 1.483000)
1.476000 0.000000 1.476000 ( 1.476000)
method_dispatch_only (ruby 1.8.6):
Test interpreted: 100k loops calling self's foo 100 times
2.170000 0.000000 2.170000 ( 2.168547)
2.230000 0.000000 2.230000 ( 2.237174)
2.240000 0.010000 2.250000 ( 2.237669)
2.170000 0.000000 2.170000 ( 2.176406)
2.170000 0.000000 2.170000 ( 2.175593)
2.230000 0.010000 2.240000 ( 2.243247)
2.240000 0.000000 2.240000 ( 2.239104)
2.170000 0.000000 2.170000 ( 2.186078)
2.230000 0.010000 2.240000 ( 2.242851)
2.170000 0.000000 2.170000 ( 2.173130)
The primitive version I made ends up breaking STI dispatch, since it
only ever dispatches through DynamicMethod objects. Also, fib numbers
seem to have taken a hit recently, perhaps my call-path generification
commit (which may get rolled back if it's the source of the problem. But
here are some interesting numbers for fib before and after call site
caching...with the cached version not using STI at all:
fib_recursive (before):
1.709000 0.000000 1.709000 ( 1.709000)
1.814000 0.000000 1.814000 ( 1.814000)
1.867000 0.000000 1.867000 ( 1.867000)
1.827000 0.000000 1.827000 ( 1.827000)
1.919000 0.000000 1.919000 ( 1.919000)
1.817000 0.000000 1.817000 ( 1.817000)
1.811000 0.000000 1.811000 ( 1.811000)
1.808000 0.000000 1.808000 ( 1.807000)
fib_recursive (after):
1.923000 0.000000 1.923000 ( 1.923000)
1.783000 0.000000 1.783000 ( 1.783000)
1.756000 0.000000 1.756000 ( 1.756000)
1.836000 0.000000 1.836000 ( 1.836000)
1.762000 0.000000 1.762000 ( 1.763000)
1.764000 0.000000 1.764000 ( 1.764000)
1.744000 0.000000 1.744000 ( 1.744000)
1.742000 0.000000 1.742000 ( 1.742000)
fib_recursive (ruby1.8.6):
1.760000 0.010000 1.770000 ( 1.823404)
1.760000 0.010000 1.770000 ( 1.944750)
1.760000 0.010000 1.770000 ( 1.924502)
1.760000 0.010000 1.770000 ( 1.826495)
1.760000 0.010000 1.770000 ( 1.841746)
1.760000 0.010000 1.770000 ( 1.825636)
1.760000 0.000000 1.760000 ( 1.830081)
1.750000 0.010000 1.760000 ( 1.824020)
So although STI fib has taken a step backward in performance, the cached
version puts us ahead of Ruby 1.8.6 again.
The ultimate for this would be to combine the fast STI dispatcher and
polymorphic call-site caches. After I find a good way to make this safe,
that's the next thing I'll look into.
Patch is attached if you want to play with it.
- Charlie
Index: src/org/jruby/runtime/CallAdapter.java
===================================================================
--- src/org/jruby/runtime/CallAdapter.java (revision 4048)
+++ src/org/jruby/runtime/CallAdapter.java (working copy)
@@ -28,6 +28,10 @@
package org.jruby.runtime;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.builtin.IRubyObject;
/**
@@ -39,6 +43,9 @@
protected final String methodName;
protected final CallType callType;
+ protected DynamicMethod method;
+ protected RubyModule methodType;
+
public CallAdapter(int methodID, String methodName, CallType callType) {
this.methodID = methodID;
this.methodName = methodName;
@@ -65,52 +72,245 @@
public IRubyObject call(ThreadContext context, Object selfObject) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, IRubyObject.NULL_ARRAY,
callType, Block.NULL_BLOCK);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = IRubyObject.NULL_ARRAY;
+ Block block = Block.NULL_BLOCK;
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject arg1) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, new IRubyObject[] {arg1},
callType, Block.NULL_BLOCK);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = new IRubyObject[] {arg1};
+ Block block = Block.NULL_BLOCK;
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject arg1, IRubyObject arg2) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, new IRubyObject[] {arg1,
arg2}, callType, Block.NULL_BLOCK);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = new IRubyObject[] {arg1, arg2};
+ Block block = Block.NULL_BLOCK;
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, new IRubyObject[] {arg1, arg2,
arg3}, callType, Block.NULL_BLOCK);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = new IRubyObject[] {arg1, arg2, arg3};
+ Block block = Block.NULL_BLOCK;
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject[] args) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, args, callType,
Block.NULL_BLOCK);
+ RubyClass rubyclass = self.getMetaClass();
+ Block block = Block.NULL_BLOCK;
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
Block block) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, IRubyObject.NULL_ARRAY,
callType, block);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = IRubyObject.NULL_ARRAY;
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject arg1, Block block) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, new IRubyObject[] {arg1},
callType, block);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = new IRubyObject[] {arg1};
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject arg1, IRubyObject arg2, Block block) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, new IRubyObject[] {arg1,
arg2}, callType, block);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = new IRubyObject[] {arg1, arg2};
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, new IRubyObject[] {arg1, arg2,
arg3}, callType, block);
+ RubyClass rubyclass = self.getMetaClass();
+ IRubyObject[] args = new IRubyObject[] {arg1, arg2, arg3};
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
public IRubyObject call(ThreadContext context, Object selfObject,
IRubyObject[] args, Block block) {
IRubyObject self = (IRubyObject)selfObject;
- return self.getMetaClass().getDispatcher().callMethod(context,
self, self.getMetaClass(), methodID, methodName, args, callType, block);
+ RubyClass rubyclass = self.getMetaClass();
+
+ try {
+ if (method != null && methodType == rubyclass) return
method.call(context, selfObject, methodType, methodName, args, block);
+ assert args != null;
+ DynamicMethod method = null;
+ method = rubyclass.searchMethod(methodName);
+
+
+ if (method.isUndefined() || (!method.equals("method_missing")
&& !method.isCallableFrom(context.getFrameSelf(), callType))) {
+ return RubyObject.callMethodMissing(context, self, method,
methodName, args, context.getFrameSelf(), callType, block);
+ }
+
+ this.method = method;
+ this.methodType = rubyclass;
+ return method.call(context, self, rubyclass, methodName, args,
block);
+ } catch (StackOverflowError soe) {
+ throw context.getRuntime().newSystemStackError("stack level
too deep");
+ }
}
}
}
Index: src/org/jruby/runtime/Dispatcher.java
===================================================================
--- src/org/jruby/runtime/Dispatcher.java (revision 4048)
+++ src/org/jruby/runtime/Dispatcher.java (working copy)
@@ -44,4 +44,8 @@
switchTable[index] = 0;
}
+
+ public boolean hasIndex(int index) {
+ return index < switchTable.length && switchTable[index] != 0;
+ }
}
---------------------------------------------------------------------
To unsubscribe from this list please visit:
http://xircles.codehaus.org/manage_email