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

Reply via email to