Here's a comparison of a purely hash-based ivar map versus one that just
does a linear search.

For objects with only a few instance variables, the linear search
mechanism is going to be faster. Perhaps what we need is a map that's
optimized so that small sizes are linear searches and after a certain
threshold it switches to hash-based?

hash map:
100k loop of 100 ivar accesses and assign to local; one ivar
   1.192000   0.000000   1.192000 (  1.192000)
   1.182000   0.000000   1.182000 (  1.182000)
   0.396000   0.000000   0.396000 (  0.396000)
   0.389000   0.000000   0.389000 (  0.389000)
   0.389000   0.000000   0.389000 (  0.389000)
100k loop of 100 ivar accesses and assign to local; two ivars
   0.391000   0.000000   0.391000 (  0.391000)
   0.391000   0.000000   0.391000 (  0.391000)
   0.389000   0.000000   0.389000 (  0.389000)
   0.389000   0.000000   0.389000 (  0.389000)
   0.390000   0.000000   0.390000 (  0.390000)
100k loop of 100 ivar accesses and assign to local; four ivars
   0.392000   0.000000   0.392000 (  0.392000)
   0.389000   0.000000   0.389000 (  0.389000)
   0.389000   0.000000   0.389000 (  0.389000)
   0.390000   0.000000   0.390000 (  0.390000)
   0.390000   0.000000   0.390000 (  0.390000)
100k loop of 100 ivar accesses and assign to local; eight ivars
   0.392000   0.000000   0.392000 (  0.392000)
   0.389000   0.000000   0.389000 (  0.390000)
   0.390000   0.000000   0.390000 (  0.390000)
   0.389000   0.000000   0.389000 (  0.389000)
   0.388000   0.000000   0.388000 (  0.388000)
100k loop of 100 ivar accesses and assign to local; sixteen ivars
   0.393000   0.000000   0.393000 (  0.393000)
   0.390000   0.000000   0.390000 (  0.390000)
   0.388000   0.000000   0.388000 (  0.388000)
   0.398000   0.000000   0.398000 (  0.398000)
   0.390000   0.000000   0.390000 (  0.390000)

linear search map:

100k loop of 100 ivar accesses and assign to local; one ivar
   1.094000   0.000000   1.094000 (  1.093000)
   1.057000   0.000000   1.057000 (  1.057000)
   0.296000   0.000000   0.296000 (  0.296000)
   0.295000   0.000000   0.295000 (  0.295000)
   0.294000   0.000000   0.294000 (  0.294000)
100k loop of 100 ivar accesses and assign to local; two ivars
   0.319000   0.000000   0.319000 (  0.319000)
   0.315000   0.000000   0.315000 (  0.315000)
   0.314000   0.000000   0.314000 (  0.314000)
   0.314000   0.000000   0.314000 (  0.314000)
   0.319000   0.000000   0.319000 (  0.319000)
100k loop of 100 ivar accesses and assign to local; four ivars
   0.350000   0.000000   0.350000 (  0.350000)
   0.354000   0.000000   0.354000 (  0.354000)
   0.348000   0.000000   0.348000 (  0.349000)
   0.349000   0.000000   0.349000 (  0.349000)
   0.350000   0.000000   0.350000 (  0.351000)
100k loop of 100 ivar accesses and assign to local; eight ivars
   0.432000   0.000000   0.432000 (  0.432000)
   0.432000   0.000000   0.432000 (  0.433000)
   0.432000   0.000000   0.432000 (  0.432000)
   0.432000   0.000000   0.432000 (  0.432000)
   0.434000   0.000000   0.434000 (  0.434000)
100k loop of 100 ivar accesses and assign to local; sixteen ivars
   0.576000   0.000000   0.576000 (  0.576000)
   0.573000   0.000000   0.573000 (  0.573000)
   0.572000   0.000000   0.572000 (  0.573000)
   0.573000   0.000000   0.573000 (  0.573000)
   0.575000   0.000000   0.575000 (  0.575000)

The bottom line is that for objects with anywhere below four to six
ivars, a really dumb linear-searching map is faster. Note also this is a
fully-synchronized map; there would certainly be smarter ways to ensure
concurrency.

- Charlie


Index: src/org/jruby/runtime/DynamicScope.java
===================================================================
--- src/org/jruby/runtime/DynamicScope.java     (revision 4182)
+++ src/org/jruby/runtime/DynamicScope.java     (working copy)
@@ -106,9 +106,8 @@
             
             parent.setValue(offset, value, depth - 1);
         } else {
-            assert offset < variableValues.length : "Setting " + offset + " to 
" + value + ", O: " + this; 
-
             lazy();
+            assert offset < variableValues.length : "Setting " + offset + " to 
" + value + ", O: " + this; 
             variableValues[offset] = value;
         }
     }
Index: src/org/jruby/RubyStruct.java
===================================================================
--- src/org/jruby/RubyStruct.java       (revision 4182)
+++ src/org/jruby/RubyStruct.java       (working copy)
@@ -189,7 +189,7 @@
 
         if (args.length > 0) {
             if (args[0] instanceof RubyString) {
-                name = args[0].toString();
+                name = args[0].toString().intern();
             } else if (args[0].isNil()) {
                 nilName = true;
             }
Index: src/org/jruby/RubyObject.java
===================================================================
--- src/org/jruby/RubyObject.java       (revision 4182)
+++ src/org/jruby/RubyObject.java       (working copy)
@@ -37,6 +37,10 @@
  ***** END LICENSE BLOCK *****/
 package org.jruby;
 
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.jruby.evaluator.EvaluationState;
 import org.jruby.exceptions.JumpException;
@@ -53,6 +57,7 @@
 import org.jruby.runtime.callback.Callback;
 import org.jruby.util.IdUtil;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -62,6 +67,187 @@
 import org.jruby.runtime.ClassIndex;
 import org.jruby.runtime.MethodIndex;
 
+class LinearSearchMap implements Map {
+    Object[] keys;
+    Object[] values;
+    
+    public LinearSearchMap() {
+        clear();
+    }
+    
+    public LinearSearchMap(Map other) {
+        clear();
+        putAll(other);
+    }
+
+    public int size() {
+        return keys.length;
+    }
+
+    public boolean isEmpty() {
+        return keys.length == 0;
+    }
+    
+    private int linearSearch(Object[] source, Object key) {
+        for (int i = 0; i < source.length; i++) if (source[i] == key) return i;
+        return -1;
+    }
+
+    public boolean containsKey(Object arg0) {
+        assert arg0 == ((String)arg0).intern();
+        return linearSearch(keys, arg0) != -1;
+    }
+
+    public boolean containsValue(Object arg0) {
+        return linearSearch(values, arg0) != -1;
+    }
+
+    public Object get(Object arg0) {
+        assert arg0 == ((String)arg0).intern();
+        int i = linearSearch(keys, arg0);
+        if (i == -1) return null;
+        return values[i];
+    }
+
+    public Object put(Object arg0, Object arg1) {
+        assert arg0 == ((String)arg0).intern();
+        int i = linearSearch(keys, arg0);
+        if (i == -1) {
+            i = grow(arg0);
+            values[i] = arg1;
+            return null;
+        }
+        arg0 = values[i];
+        values[i] = arg1;
+        return arg0;
+    }
+    
+    private int grow(Object key) {
+        Object[] newKeys = new Object[keys.length + 1];
+        System.arraycopy(keys, 0, newKeys, 0, keys.length);
+        keys = newKeys;
+        keys[keys.length - 1] = key;
+        Object[] newValues = new Object[values.length + 1];
+        System.arraycopy(values, 0, newValues, 0, values.length);
+        values = newValues;
+        return keys.length - 1;
+    }
+
+    public Object remove(Object arg0) {
+        assert arg0 == ((String)arg0).intern();
+        int i = Arrays.binarySearch(keys, arg0);
+        if (i == -1) return null;
+        return remove(i);
+    }
+    
+    private Object remove(int i) {
+        Object[] newKeys = new Object[keys.length - 1];
+        System.arraycopy(keys, 0, newKeys, 0, i);
+        System.arraycopy(keys, i + 1, newKeys, i, keys.length - (i + 1));
+        Object old = keys[i];
+        keys = newKeys;
+        return old;
+    }
+
+    public void putAll(Map arg0) {
+        for (Map.Entry entry : (Set<Map.Entry>)arg0.entrySet()) {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public void clear() {
+        keys = new Object[0];
+        values = new Object[0];
+    }
+
+    public Set keySet() {
+        return new AbstractSet() {
+            Object[] setKeys = keys;
+            
+            public Iterator iterator() {
+                return new Iterator() {
+                    int i = -1;
+
+                    public boolean hasNext() {
+                        return i + 1 < setKeys.length;
+                    }
+
+                    public Object next() {
+                        if (i + 1 >= setKeys.length) {
+                            throw new ArrayIndexOutOfBoundsException();
+                        }
+                        return keys[++i];
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException("Not supported 
yet.");
+                    }
+                    
+                };
+            }
+
+            public int size() {
+                return keys.length;
+            }
+            
+        };
+    }
+
+    public Collection values() {
+        return Arrays.asList(values);
+    }
+
+    public Set entrySet() {
+        return new AbstractSet() {
+            Object[] setKeys = keys;
+            Object[] setValues = values;
+            
+            public Iterator iterator() {
+                return new Iterator() {
+                    int i = -1;
+
+                    public boolean hasNext() {
+                        return i + 1 < setKeys.length;
+                    }
+
+                    public Object next() {
+                        if (i + 1 >= setKeys.length) {
+                            throw new ArrayIndexOutOfBoundsException();
+                        }
+                        ++i;
+                        return new Map.Entry() {
+                            Object key = setKeys[i];
+                            Object value = setValues[i];
+                            public Object getKey() {
+                                return key;
+                            }
+
+                            public Object getValue() {
+                                return value;
+                            }
+
+                            public Object setValue(Object arg0) {
+                                throw new UnsupportedOperationException("Not 
supported yet.");
+                            }
+                            
+                        };
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException("Not supported 
yet.");
+                    }
+                    
+                };
+            }
+
+            public int size() {
+                return keys.length;
+            }
+            
+        };
+    }
+}
+
 /**
  *
  * @author  jpetersen
@@ -231,7 +417,7 @@
     }
     
     public static void puts(Object obj) {
-        System.out.println(obj.toString());
+        System.out.println(obj == null ? "null" : obj.toString());
     }
 
     /**
@@ -283,7 +469,7 @@
        if (instanceVariables == null) {
                synchronized (this) {
                        if (instanceVariables == null) {
-                            instanceVariables = 
Collections.synchronizedMap(new HashMap());
+                            instanceVariables = new LinearSearchMap();
                        }
                }
        }
@@ -291,7 +477,7 @@
     }
 
     public void setInstanceVariables(Map instanceVariables) {
-        this.instanceVariables = 
Collections.synchronizedMap(instanceVariables);
+        this.instanceVariables = new LinearSearchMap(instanceVariables);
     }
 
     /**
Index: src/org/jruby/RubyModule.java
===================================================================
--- src/org/jruby/RubyModule.java       (revision 4182)
+++ src/org/jruby/RubyModule.java       (working copy)
@@ -461,6 +461,7 @@
      * @see RubyObject#setInstanceVariable(String, IRubyObject, String, String)
      */
     public IRubyObject setConstant(String name, IRubyObject value) {
+        name = name.intern();
         IRubyObject oldValue = getInstanceVariable(name);
         
         if (oldValue == getRuntime().getUndef()) {
@@ -1094,7 +1095,7 @@
             attributeScope = Visibility.PRIVATE;
             // FIXME warning
         }
-        final String variableName = "@" + name;
+        final String variableName = ("@" + name).intern();
         final Ruby runtime = getRuntime();
         ThreadContext context = getRuntime().getCurrentContext();
         if (readable) {


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

    http://xircles.codehaus.org/manage_email

Reply via email to