Attached is a new patch for Java return-value coercion performance. It solves the issues shown in the following benchmark:

BEFORE
Measure System.currentTimeMillis, int becoming Fixnum
  6.254000   0.000000   6.254000 (  6.254000)
  6.262000   0.000000   6.262000 (  6.262000)
  6.177000   0.000000   6.177000 (  6.177000)
  6.094000   0.000000   6.094000 (  6.094000)
  6.331000   0.000000   6.331000 (  6.331000)
Measure string.length, integer length to fixnum
  0.720000   0.000000   0.720000 (  0.720000)
  0.521000   0.000000   0.521000 (  0.521000)
  0.568000   0.000000   0.568000 (  0.568000)
  0.514000   0.000000   0.514000 (  0.514000)
  0.518000   0.000000   0.518000 (  0.518000)

AFTER
Measure System.currentTimeMillis, int becoming Fixnum
  1.054000   0.000000   1.054000 (  1.055000)
  0.940000   0.000000   0.940000 (  0.940000)
  0.953000   0.000000   0.953000 (  0.953000)
  1.001000   0.000000   1.001000 (  1.000000)
  0.935000   0.000000   0.935000 (  0.936000)
Measure string.length, integer length to fixnum
  0.592000   0.000000   0.592000 (  0.593000)
  0.496000   0.000000   0.496000 (  0.496000)
  0.556000   0.000000   0.556000 (  0.555000)
  0.484000   0.000000   0.484000 (  0.484000)
  0.481000   0.000000   0.481000 (  0.481000)

But it fails some of our own tests, specifically for "lower" Java support which allows you to work with Java method objects and types directly. I'll explain why...

In the original code, JavaMethod always just returns the result wrapped in a JavaObject. This meant that all values, even values that would eventually be coerced to Ruby types, would get both wrapped and cached for identity purposes. This is a large reason why Java integration is so slow for those coercible types.

In the new code, JavaMethod is modified to always return the most-specific, most-coerced type of object, only wrapping and caching in cases where no coercion is possible. This means that Strings and primitives (so far) go straight from java.lang.reflect.Method.invoke's return value into an appropriate Ruby object type.

What breaks is the ability to go to lower-level Java support, get a JavaMethod object directly, and invoke it to get the original wrapped JavaObject. Largely this capability was used in the old JI code to implement the normal Java integration features; with most of those "higher" Java support features now in 100% Java code, this may not be necessary anymore.

So I think Bill and I are going to need to get together to determine how this is all supposed to fit together in the future. I'm not comfortable just breaking lower-level Java support across the board without knowing if I'm breaking something higher first. But the split between lower and higher and the overhead of wrapping so many objects is probably going to have to go.

- Charlie
Index: src/org/jruby/javasupport/JavaUtil.java
===================================================================
--- src/org/jruby/javasupport/JavaUtil.java     (revision 4047)
+++ src/org/jruby/javasupport/JavaUtil.java     (working copy)
@@ -36,10 +36,13 @@
 import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.jruby.Ruby;
 import org.jruby.RubyBignum;
 import org.jruby.RubyBoolean;
+import org.jruby.RubyFixnum;
 import org.jruby.RubyFloat;
 import org.jruby.RubyModule;
 import org.jruby.RubyNumeric;
@@ -178,6 +181,102 @@
         }
         return rubyObjects;
     }
+    
+    public interface JavaConverter {
+        public IRubyObject convert(Ruby runtime, Object object);
+    }
+    
+    public static final JavaConverter JAVA_DEFAULT_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return convertJavaToRuby(runtime, object);
+        }
+    };
+    
+    public static final JavaConverter JAVA_BOOLEAN_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyBoolean.newBoolean(runtime, 
((Boolean)object).booleanValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_FLOAT_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFloat.newFloat(runtime, ((Float)object).doubleValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_DOUBLE_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFloat.newFloat(runtime, ((Double)object).doubleValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_CHAR_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFixnum.newFixnum(runtime, 
((Character)object).charValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_BYTE_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFixnum.newFixnum(runtime, ((Byte)object).byteValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_SHORT_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFixnum.newFixnum(runtime, ((Short)object).shortValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_INT_CONVERTER = new JavaConverter() 
{
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFixnum.newFixnum(runtime, ((Integer)object).intValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_LONG_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyFixnum.newFixnum(runtime, ((Long)object).longValue());
+        }
+    };
+    
+    public static final JavaConverter JAVA_STRING_CONVERTER = new 
JavaConverter() {
+        public IRubyObject convert(Ruby runtime, Object object) {
+            return RubyString.newString(runtime, (String)object);
+        }
+    };
+    
+    private static final Map JAVA_CONVERTERS = new HashMap();
+    
+    static {
+        JAVA_CONVERTERS.put(Byte.class, JAVA_BYTE_CONVERTER);
+        JAVA_CONVERTERS.put(Byte.TYPE, JAVA_BYTE_CONVERTER);
+        JAVA_CONVERTERS.put(Short.class, JAVA_SHORT_CONVERTER);
+        JAVA_CONVERTERS.put(Short.TYPE, JAVA_SHORT_CONVERTER);
+        JAVA_CONVERTERS.put(Character.class, JAVA_CHAR_CONVERTER);
+        JAVA_CONVERTERS.put(Character.TYPE, JAVA_CHAR_CONVERTER);
+        JAVA_CONVERTERS.put(Integer.class, JAVA_INT_CONVERTER);
+        JAVA_CONVERTERS.put(Integer.TYPE, JAVA_INT_CONVERTER);
+        JAVA_CONVERTERS.put(Float.class, JAVA_FLOAT_CONVERTER);
+        JAVA_CONVERTERS.put(Float.TYPE, JAVA_FLOAT_CONVERTER);
+        JAVA_CONVERTERS.put(Double.class, JAVA_DOUBLE_CONVERTER);
+        JAVA_CONVERTERS.put(Double.TYPE, JAVA_DOUBLE_CONVERTER);
+        JAVA_CONVERTERS.put(Boolean.class, JAVA_BOOLEAN_CONVERTER);
+        JAVA_CONVERTERS.put(Boolean.TYPE, JAVA_BOOLEAN_CONVERTER);
+        
+        JAVA_CONVERTERS.put(String.class, JAVA_STRING_CONVERTER);
+    }
+    
+    public static JavaConverter getJavaConverter(Class clazz) {
+        JavaConverter converter = (JavaConverter)JAVA_CONVERTERS.get(clazz);
+        
+        if (converter == null) {
+            converter = JAVA_DEFAULT_CONVERTER;
+            JAVA_CONVERTERS.put(clazz, converter);
+        }
+        
+        return converter;
+    }
 
     public static IRubyObject convertJavaToRuby(Ruby runtime, Object object) {
         if (object == null) {
Index: src/org/jruby/javasupport/JavaMethod.java
===================================================================
--- src/org/jruby/javasupport/JavaMethod.java   (revision 4047)
+++ src/org/jruby/javasupport/JavaMethod.java   (working copy)
@@ -56,6 +56,7 @@
 public class JavaMethod extends JavaCallable {
     private final Method method;
     private final Class[] parameterTypes;
+    private final JavaUtil.JavaConverter returnConverter;
 
     public static RubyClass createJavaMethodClass(Ruby runtime, RubyModule 
javaModule) {
         // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here, since we don't 
intend for people to monkey with
@@ -93,6 +94,8 @@
             !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
             accesibleObject().setAccessible(true);
         }
+        
+        returnConverter = JavaUtil.getJavaConverter(method.getReturnType());
     }
 
     public static JavaMethod create(Ruby runtime, Method method) {
@@ -194,7 +197,7 @@
     private IRubyObject invokeWithExceptionHandling(Method method, Object 
javaInvokee, Object[] arguments) {
         try {
             Object result = method.invoke(javaInvokee, arguments);
-            return JavaObject.wrap(getRuntime(), result);
+            return returnConverter.convert(getRuntime(), result);
         } catch (IllegalArgumentException iae) {
             throw getRuntime().newTypeError("expected " + 
argument_types().inspect() + "; got: "
                         + dumpArgTypes(arguments)

---------------------------------------------------------------------
To unsubscribe from this list please visit:

    http://xircles.codehaus.org/manage_email

Reply via email to