Przeglądaj źródła

Column#entry - support for common Map<> implementations

Ranides Atterwim 12 lat temu
rodzic
commit
6de5107f54

+ 68 - 17
src/main/java/net/ranides/assira/collection/map/Column.java

@@ -12,12 +12,17 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
 import net.ranides.assira.reflection.ClassInspector;
 import net.ranides.assira.reflection.Invoker;
+import net.ranides.assira.reflection.ObjectInspector;
 
 /**
  *
  * @author Mariusz Sieron <m.sieron@samsung.com>
+ * @param <K>
+ * @param <V>
  */
 public class Column<K,V> {
 
@@ -46,45 +51,91 @@ public class Column<K,V> {
     }
 
     public static <K,V> Entry<K,V> entry(Map<K, V> map, K key) {
-        // @todo (assira # 5) MapEntry#get - support
-        //  - EnumMap
-        //  - WeakHashMap
-        //  - ConcurrentHashMap
-        //  - ConcurrentSkipListMap $SubMap
-        //  - TreeMap.NavigableSubMap
-        //  - IdentityHashMap
-        //  - assira.xml.NodeCollection.LMap
-
-        if( map.isEmpty() ) {
-            return null;
-        }
-        if(map instanceof TreeMap<?,?>) {
+        if(map instanceof TreeMap) {
             return (Map.Entry<K,V>)Invoker.invoke(map, EntryAccess.TREE, key);
         }
-        if(map instanceof HashMap<?,?>) {
+        if(EntryAccess.CS_TREE_SUBMAP.isInstance(map)) {
+            Object treemap = ObjectInspector.getField(EntryAccess.CS_TREE_SUBMAP, map, "m");
+            return (Map.Entry<K,V>)Invoker.invoke(treemap, EntryAccess.TREE, key);
+        }
+        if(map instanceof HashMap) {
             return (Map.Entry<K,V>)Invoker.invoke(map, EntryAccess.HASH, key);
         }
-        return new EntryView<K, V>(map, key);
+        if(map instanceof WeakHashMap) {
+            return (Map.Entry<K,V>)Invoker.invoke(map, EntryAccess.WEAK, key);
+        }
+        // it's slower than default "virtual" entry
+//        if(map instanceof ConcurrentHashMap) {
+//            return new CHEntry<K,V>(map, key);
+//        }
+        return new DVEntry<K, V>(map, key);
     }
 
     private static final class EntryAccess {
 
         private static final Method TREE;
         private static final Method HASH;
+        private static final Method WEAK;
+
+        private static final Method CH_SEGMENT;
+        private static final Method CH_HASH;
+        private static final Method CH_GET;
+        private static final Method CH_PUT;
+
+        private static final Class<?> CS_TREE_SUBMAP;
 
         static {
             TREE = ClassInspector.findDeclaredMethod(TreeMap.class, "getEntry");
+            CS_TREE_SUBMAP = ClassInspector.forName("java.util.TreeMap$NavigableSubMap");
+
             HASH = ClassInspector.findDeclaredMethod(HashMap.class, "getEntry");
+            WEAK = ClassInspector.findDeclaredMethod(WeakHashMap.class, "getEntry");
+
+            CH_SEGMENT = ClassInspector.getDeclaredMethod(ConcurrentHashMap.class, "segmentFor", int.class);
+            CH_HASH = ClassInspector.getDeclaredMethod(ConcurrentHashMap.class, "hash", int.class);
+
+            Class<?> CS_SEGMENT = ClassInspector.forName("java.util.concurrent.ConcurrentHashMap$Segment");
+            CH_GET = ClassInspector.findDeclaredMethod(CS_SEGMENT, "get");
+            CH_PUT = ClassInspector.findDeclaredMethod(CS_SEGMENT, "put");
+        }
+
+    }
+
+    private static final class CHEntry<K,V> implements Map.Entry<K, V> {
+
+        private final K key;
+        private final int hash;
+        private final Object segment;
+
+        public CHEntry(Map<K,V> map, K key) {
+            this.key = key;
+            this.hash = (Integer)Invoker.invoke(null, EntryAccess.CH_HASH, key.hashCode());
+            this.segment = Invoker.invoke(map, EntryAccess.CH_SEGMENT, hash);
+        }
+
+        @Override
+        public K getKey() {
+            return key;
+        }
+
+        @Override
+        public V getValue() {
+            return (V)Invoker.invoke(segment, EntryAccess.CH_GET, key, hash);
+        }
+
+        @Override
+        public V setValue(V value) {
+            return (V)Invoker.invoke(segment, EntryAccess.CH_PUT, key, hash, value, false);
         }
 
     }
 
-    private static final class EntryView<K,V> implements Map.Entry<K, V> {
+    private static final class DVEntry<K,V> implements Map.Entry<K, V> {
 
         private final Map<K,V> map;
         private final K key;
 
-        public EntryView(Map<K, V> map, K key) {
+        public DVEntry(Map<K, V> map, K key) {
             this.map = map;
             this.key = key;
         }

+ 5 - 0
src/main/java/net/ranides/assira/time/TimeStack.java

@@ -38,5 +38,10 @@ public class TimeStack {
         return times.isEmpty() ? 0 : new Date().getTime() - times.lastElement();
     }
 
+    final public long restart() {
+        long prev = stop();
+        start();
+        return prev;
+    }
 
 }

+ 75 - 0
src/test/java/net/ranides/assira/collection/map/ColumnTest.java

@@ -12,13 +12,18 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
 import net.ranides.assira.asm.MethodInspector;
+import net.ranides.assira.math.Randomizer;
 import net.ranides.assira.reflection.ClassInspector;
 import net.ranides.assira.reflection.Invoker;
 import net.ranides.assira.reflection.MethodPointer;
 import net.ranides.assira.text.format.AnsiString;
+import net.ranides.assira.time.TimeResult;
+import net.ranides.assira.time.TimeStack;
 import org.junit.Test;
 import static org.junit.Assert.*;
+import org.junit.Ignore;
 
 /**
  *
@@ -74,9 +79,11 @@ public class ColumnTest {
 
         Map<String, Integer> map1 = new HashMap<String, Integer>();
         Map<String, Integer> map2 = new TreeMap<String, Integer>();
+        Map<String, Integer> map3 = new ConcurrentHashMap<String, Integer>();
 
         map1.put("a", 17);
         map2.put("b", 14);
+        map3.put("c", 11);
 
         final Entry<String,Integer> entry1 = Column.entry(map1, "a");
         entry1.setValue(entry1.getValue() + 1);
@@ -84,8 +91,12 @@ public class ColumnTest {
         final Entry<String,Integer> entry2 = Column.entry(map2, "b");
         entry2.setValue(entry2.getValue() + 1);
 
+        final Entry<String,Integer> entry3 = Column.entry(map3, "c");
+        entry3.setValue(entry3.getValue() + 1);
+
         assertEquals( (Integer)18, map1.get("a"));
         assertEquals( (Integer)15, map2.get("b"));
+        assertEquals( (Integer)12, map3.get("c"));
     }
 
     @Test
@@ -110,4 +121,68 @@ public class ColumnTest {
         assertEquals( (Integer)15, map2.get("b"));
     }
 
+    /**
+     * Number of keys = 128'000
+     *      | put   opt   vvi  | det
+     * -----+------------------+-------
+     * Tree | 8800  6400  -    |  72 %
+     * Hash | 1300  1600  1800 | 123 %
+     * Conc | 2000  4100  2300 | 115 %
+     *
+     * Number of keys = 128
+     *      | put   opt   vvi  | det
+     * -----+------------------+-------
+     * Tree | 6500  4600  -    |  70 %
+     * Hash | 1800  1600  3200 |  88 %
+     * Conc | 5700  11600 6700 | 117 %
+     */
+//    @Ignore("manual slow")
+    @Test
+    public void testPerfEntry() {
+
+        String[] keys = new String[128];
+        for(int i=0, n=keys.length; i<n; i++) {
+            keys[i] = Randomizer.text(32);
+        }
+
+        Map<String, Integer> treemap = new TreeMap<String, Integer>();
+        Map<String, Integer> hashmap = new HashMap<String, Integer>();
+        Map<String, Integer> concmap = new ConcurrentHashMap<String, Integer>();
+        for(String key : keys) {
+            treemap.put(key, Randomizer.number(1000));
+            hashmap.put(key, Randomizer.number(1000));
+            concmap.put(key, Randomizer.number(1000));
+        }
+
+        runPerfEntry(keys, 1000000, treemap);
+        runPerfEntry(keys, 1000000, hashmap);
+        runPerfEntry(keys, 1000000, concmap);
+    }
+
+    private void runPerfEntry(String[] keys, int count, Map<String, Integer> map) {
+        TimeStack ts = new TimeStack();
+
+        ts.start();
+        for(int t=0; t<count; t++) {
+            for(String key : keys) {
+                map.put(key, map.get(key)+1);
+            }
+        }
+
+        long t1 = ts.restart();
+
+        for(int t=0; t<count; t++) {
+            for(String key : keys) {
+                final Entry<String, Integer> entry = Column.entry(map, key);
+                entry.setValue(entry.getValue()+1);
+            }
+        }
+
+        long t2 = ts.stop();
+
+        System.out.println("time = " + t1 + " / " + t2);
+
+    }
+
+
 }