BUG-7464: Optimize LNode operations 22/49922/11
authorRobert Varga <rovarga@cisco.com>
Mon, 2 Jan 2017 02:30:11 +0000 (03:30 +0100)
committerRobert Varga <rovarga@cisco.com>
Tue, 10 Jan 2017 19:12:11 +0000 (20:12 +0100)
Refactor ListMap into LNodeEntr{y,ies} and expose better
operations for node manipulation, since performance-critical
call sites already perform a prior lookup.

Therefore we can issue explicit insert/replace operations
insteads of generic 'add'. Insert operation becomes very fast,
as it does not need to perform any checking at all.

The replace operation does not require equivalence, as it
can use identity comparison on the entry, again improving
performance. Furthermore we can rely on the fact that
a particular entry is present exactly once, hence we can
stop iteration as soon as we find it.

Change-Id: I8c28e3521f1cbc298164b84b51dc300c3f3568a9
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/INode.java
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNode.java
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNodeEntries.java [new file with mode: 0644]
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNodeEntry.java [new file with mode: 0644]
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ListMap.java [deleted file]
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/TrieMap.java
third-party/triemap/src/test/java/org/opendaylight/yangtools/triemap/ListMapTest.java

index bc2a4d000a614d96bebbdcce550787f04de5d1a0..94f10ce901df730d02d8ae4beca1b9c263586b60 100644 (file)
@@ -132,7 +132,7 @@ final class INode<K, V> extends BasicNode {
     private boolean rec_insert(final K k, final V v, final int hc, final int lev, final INode<K, V> parent,
             final Gen startgen, final TrieMap<K, V> ct) {
         while (true) {
-            final MainNode<K, V> m = GCAS_READ (ct); // use -Yinline!
+            final MainNode<K, V> m = GCAS_READ(ct); // use -Yinline!
 
             if (m instanceof CNode) {
                 // 1) a multiway node
@@ -176,7 +176,9 @@ final class INode<K, V> extends BasicNode {
                 clean(parent, ct, lev - 5);
                 return false;
             } else if (m instanceof LNode) {
-                return insertln((LNode<K, V>) m, k, v, ct);
+                final LNode<K, V> ln = (LNode<K, V>) m;
+                final LNodeEntry<K, V> entry = ln.get(ct.equiv(), k);
+                return entry != null ? replaceln(ln, entry, v, ct) : insertln(ln, k, v, ct);
             } else {
                 throw new IllegalStateException("Unhandled node " + m);
             }
@@ -300,45 +302,32 @@ final class INode<K, V> extends BasicNode {
             } else if (m instanceof LNode) {
                 // 3) an l-node
                 final LNode<K, V> ln = (LNode<K, V>) m;
+                final LNodeEntry<K, V> entry = ln.get(ct.equiv(), k);
+
                 if (cond == null) {
-                    final Optional<V> optv = ln.get(ct.equiv(), k);
-                    if (insertln(ln, k, v, ct)) {
-                        return optv;
+                    if (entry != null) {
+                        return replaceln(ln, entry, v, ct) ? Optional.of(entry.value()) : null;
                     }
-                    return null;
+
+                    return insertln(ln, k, v, ct) ? Optional.empty() : null;
                 } else if (cond == ABSENT) {
-                    final Optional<V> t = ln.get(ct.equiv(),k);
-                    if (t.isPresent()) {
-                        return t;
+                    if (entry != null) {
+                        return Optional.of(entry.value());
                     }
-                    if (insertln(ln, k, v, ct)) {
-                        return Optional.empty();
-                    }
-                    return null;
+
+                    return insertln(ln, k, v, ct) ? Optional.empty() : null;
                 } else if (cond == PRESENT) {
-                    final Optional<V> t = ln.get(ct.equiv(),k);
-                    if (!t.isPresent()) {
-                        return t;
-                    }
-                    if (insertln(ln, k, v, ct)) {
-                        return t;
+                    if (entry == null) {
+                        return Optional.empty();
                     }
-                    return null;
-                } else {
-                    final Optional<V> t = ln.get(ct.equiv(),k);
-                    if (t.isPresent()) {
-                        if (cond.equals(t.get())) {
-                            if (insertln(ln, k, v, ct)) {
-                                // Difference from Scala: we choose to reuse the object returned from LNode,
-                                // as the identity of the value does not matter in this call graph.
-                                return t;
-                            }
 
-                            return null;
-                        }
+                    return replaceln(ln, entry, v, ct) ? Optional.of(entry.value()) : null;
+                } else {
+                    if (entry == null || !cond.equals(entry.value())) {
+                        return Optional.empty();
                     }
 
-                    return Optional.empty();
+                    return replaceln(ln, entry, v, ct) ? Optional.of(entry.value()) : null;
                 }
             } else {
                 throw new IllegalStateException("Unhandled node " + m);
@@ -349,7 +338,11 @@ final class INode<K, V> extends BasicNode {
     }
 
     private boolean insertln(final LNode<K, V> ln, final K k, final V v, final TrieMap<K, V> ct) {
-        return GCAS(ln, ln.addChild(ct.equiv(), k, v), ct);
+        return GCAS(ln, ln.insertChild(k, v), ct);
+    }
+
+    private boolean replaceln(final LNode<K, V> ln, final LNodeEntry<K, V> entry, final V v, final TrieMap<K, V> ct) {
+        return GCAS(ln, ln.replaceChild(entry, v), ct);
     }
 
     /**
@@ -407,7 +400,8 @@ final class INode<K, V> extends BasicNode {
                 return cleanReadOnly((TNode<K, V>) m, lev, parent, ct, k, hc);
             } else if (m instanceof LNode) {
                 // 5) an l-node
-                return ((LNode<K, V>) m).get(ct.equiv(), k).orElse(null);
+                final LNodeEntry<K, V> entry = ((LNode<K, V>) m).get(ct.equiv(), k);
+                return entry != null ? entry.value() : null;
             } else {
                 throw new IllegalStateException("Unhandled node " + m);
             }
@@ -505,19 +499,19 @@ final class INode<K, V> extends BasicNode {
             return null;
         } else if (m instanceof LNode) {
             final LNode<K, V> ln = (LNode<K, V>) m;
-            final Optional<V> optv = ln.get(ct.equiv(), k);
-
-            if (!optv.isPresent()) {
+            final LNodeEntry<K, V> entry = ln.get(ct.equiv(), k);
+            if (entry == null) {
                 // Key was not found, hence no modification is needed
                 return Optional.empty();
             }
 
-            if (v != null && !v.equals(optv.get())) {
+            final V value = entry.value();
+            if (v != null && !v.equals(value)) {
                 // Value does not match
                 return Optional.empty();
             }
 
-            return GCAS(ln, ln.removeChild(ct.equiv(), k, hc), ct) ? optv : null;
+            return GCAS(ln, ln.removeChild(entry, hc), ct) ? Optional.of(value) : null;
         } else {
             throw new IllegalStateException("Unhandled node " + m);
         }
index 17c3c3e74bc1a85608fe58d21ba9ab41ab947ed8..0bc4c925258a065001353aaa09bcc9c3380cf165 100644 (file)
@@ -20,24 +20,24 @@ import java.util.Map.Entry;
 import java.util.Optional;
 
 final class LNode<K, V> extends MainNode<K, V> {
-    private final ListMap<K, V> listmap;
+    private final LNodeEntries<K, V> listmap;
 
-    private LNode(final ListMap<K, V> listmap) {
+    private LNode(final LNodeEntries<K, V> listmap) {
         this.listmap = listmap;
     }
 
     LNode(final K k1, final V v1, final K k2, final V v2) {
-        this(ListMap.map(k1, v1, k2, v2));
+        this(LNodeEntries.map(k1, v1, k2, v2));
     }
 
-    LNode<K, V> addChild(final Equivalence<? super K> equiv, final K k, final V v) {
-        return new LNode<>(listmap.add(equiv, k, v));
+    LNode<K, V> insertChild( final K k, final V v) {
+        return new LNode<>(listmap.insert(k, v));
     }
 
-    MainNode<K, V> removeChild(final Equivalence<? super K> equiv, final K k, final int hc) {
+    MainNode<K, V> removeChild(final LNodeEntry<K, V> entry, final int hc) {
         // We only ever create ListMaps with two or more entries,  and remove them as soon as they reach one element
         // (below), so we cannot observe a null return here.
-        final ListMap<K, V> map = listmap.remove(equiv, k);
+        final LNodeEntries<K, V> map = listmap.remove(entry);
         final Optional<Entry<K, V>> maybeKv = map.maybeSingleton();
         if (maybeKv.isPresent()) {
             final Entry<K, V> kv = maybeKv.get();
@@ -48,8 +48,12 @@ final class LNode<K, V> extends MainNode<K, V> {
         return new LNode<>(map);
     }
 
-    Optional<V> get(final Equivalence<? super K> equiv, final K k) {
-        return listmap.get(equiv, k);
+    MainNode<K, V> replaceChild(final LNodeEntry<K, V> entry, final V v) {
+        return new LNode<>(listmap.replace(entry, v));
+    }
+
+    LNodeEntry<K, V> get(final Equivalence<? super K> equiv, final K k) {
+        return listmap.findEntry(equiv, k);
     }
 
     @Override
@@ -66,4 +70,6 @@ final class LNode<K, V> extends MainNode<K, V> {
     Iterator<Entry<K, V>> iterator() {
         return listmap.iterator();
     }
+
+
 }
\ No newline at end of file
diff --git a/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNodeEntries.java b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNodeEntries.java
new file mode 100644 (file)
index 0000000..93382a9
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * (C) Copyright 2016 Pantheon Technologies, s.r.o. and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opendaylight.yangtools.triemap;
+
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+/**
+ * Similar to Scala's ListMap. Stores a linked set of entries, guaranteed to contain unique entry keys.
+ *
+ * @author Robert Varga
+ *
+ * @param <K> the type of keys
+ * @param <V> the type of values
+ */
+final class LNodeEntries<K, V> extends LNodeEntry<K, V> {
+    // Modified during remove0 only
+    private LNodeEntries<K, V> next;
+
+    private LNodeEntries(final K k, final V v) {
+        this(k, v, null);
+    }
+
+    private LNodeEntries(final K k, final V v, final LNodeEntries<K, V> next) {
+        super(k, v);
+        this.next = next;
+    }
+
+    static <K,V> LNodeEntries<K, V> map(final K k1, final V v1, final K k2, final V v2) {
+        return new LNodeEntries<>(k1, v1, new LNodeEntries<>(k2, v2));
+    }
+
+    Optional<Entry<K, V>> maybeSingleton() {
+        return next != null ? Optional.empty() : Optional.of(new SimpleImmutableEntry<>(key(), value()));
+    }
+
+    int size() {
+        int sz = 1;
+        for (LNodeEntries<?, ?> wlk = next; wlk != null; wlk = wlk.next) {
+            sz++;
+        }
+        return sz;
+    }
+
+    LNodeEntry<K, V> findEntry(final Equivalence<? super K> equiv, final K key) {
+        // We do not perform recursion on purpose here, so we do not run out of stack if the key hashing fails.
+        LNodeEntries<K, V> head = this;
+        do {
+            if (equiv.equivalent(head.key(), key)) {
+                return head;
+            }
+
+            head = head.next;
+        } while (head != null);
+
+        return null;
+    }
+
+    LNodeEntries<K,V> insert(final K key, final V value) {
+        return new LNodeEntries<>(key, value, this);
+    }
+
+    LNodeEntries<K, V> replace(final LNodeEntry<K, V> entry, final V v) {
+        return new LNodeEntries<>(entry.key(), v, remove(entry));
+    }
+
+    LNodeEntries<K, V> remove(final LNodeEntry<K, V> entry) {
+        if (entry == this) {
+            return next;
+        }
+
+        final LNodeEntries<K, V> ret = new LNodeEntries<>(key(), value());
+
+        LNodeEntries<K, V> last = ret;
+        LNodeEntries<K, V> cur = next;
+        while (cur != null) {
+            if (entry.equals(cur)) {
+                last.next = cur.next;
+                return ret;
+            }
+
+            last.next = new LNodeEntries<>(cur.key(), cur.value());
+            last = last.next;
+            cur = cur.next;
+        }
+
+        throw new IllegalStateException("Entry " + entry + " not found in entries " + this);
+    }
+
+    Iterator<Entry<K, V>> iterator() {
+        return new NodeIterator<>(this);
+    }
+
+    static final class NodeIterator<K,V> implements Iterator<Entry<K, V>> {
+        private LNodeEntries<K, V> n;
+
+        NodeIterator(final LNodeEntries<K, V> n) {
+            this.n = n;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return n != null;
+        }
+
+        @Override
+        public Entry<K, V> next() {
+            if (n == null) {
+                throw new NoSuchElementException();
+            }
+
+            final Entry<K, V> res = new SimpleImmutableEntry<>(n.key(), n.value());
+            n = n.next;
+            return res;
+        }
+    }
+}
diff --git a/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNodeEntry.java b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/LNodeEntry.java
new file mode 100644 (file)
index 0000000..66c11b7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * (C) Copyright 2016 Pantheon Technologies, s.r.o. and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opendaylight.yangtools.triemap;
+
+/**
+ * A single entry in {@link LNodeEntries}.
+ *
+ * @author Robert Varga
+ *
+ * @param <K> the type of key
+ * @param <V> the type of value
+ */
+abstract class LNodeEntry<K, V> {
+    private final V value;
+    private final K key;
+
+    LNodeEntry(final K key, final V value) {
+        this.value = value;
+        this.key = key;
+    }
+
+    final K key() {
+        return key;
+    }
+
+    final V value() {
+        return value;
+    }
+
+}
diff --git a/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ListMap.java b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ListMap.java
deleted file mode 100644 (file)
index b2f1c10..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * (C) Copyright 2016 Pantheon Technologies, s.r.o. and others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.opendaylight.yangtools.triemap;
-
-import java.util.AbstractMap.SimpleImmutableEntry;
-import java.util.Iterator;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-import java.util.Optional;
-
-/**
- * Mimic immutable ListMap in Scala
- *
- * @author Roman Levenstein <romixlev@gmail.com>
- *
- * @param <V>
- */
-final class ListMap<K, V> {
-    private final K k;
-    private final V v;
-
-    // Modified during remove0 only
-    private ListMap<K, V> next;
-
-    private ListMap(final K k, final V v) {
-        this(k, v, null);
-    }
-
-    private ListMap(final K k, final V v, final ListMap<K, V> next) {
-        this.k = k;
-        this.v = v;
-        this.next = next;
-    }
-
-    static <K,V> ListMap<K, V> map(final K k1, final V v1, final K k2, final V v2) {
-        return new ListMap<>(k1, v1, new ListMap<>(k2, v2));
-    }
-
-    Optional<Entry<K, V>> maybeSingleton() {
-        return next != null ? Optional.empty() : Optional.of(new SimpleImmutableEntry<>(k, v));
-    }
-
-    int size() {
-        int sz = 1;
-        for (ListMap<?, ?> wlk = next; wlk != null; wlk = wlk.next) {
-            sz++;
-        }
-        return sz;
-    }
-
-    Optional<V> get(final Equivalence<? super K> equiv, final K key) {
-        // We do not perform recursion on purpose here, so we do not run out of stack if the key hashing fails.
-        ListMap<K, V> head = this;
-        do {
-            if (equiv.equivalent(head.k, key)) {
-                return Optional.of(head.v);
-            }
-
-            head = head.next;
-        } while (head != null);
-
-        return Optional.empty();
-    }
-
-    ListMap<K,V> add(final Equivalence<? super K> equiv, final K key, final V value) {
-        return new ListMap<>(key, value, remove(equiv, key));
-    }
-
-    ListMap<K, V> remove(final Equivalence<? super K> equiv, final K key) {
-         if (!contains(equiv, key)) {
-            return this;
-        }
-
-        return remove0(key);
-    }
-
-    private boolean contains(final Equivalence<? super K> equiv, final K key) {
-        // We do not perform recursion on purpose here, so we do not run out of stack if the key hashing fails.
-        ListMap<K, V> head = this;
-        do {
-            if (equiv.equivalent(head.k, key)) {
-                return true;
-            }
-            head = head.next;
-        } while (head != null);
-
-        return false;
-    }
-
-    private ListMap<K, V> remove0(final K key) {
-        ListMap<K, V> n = this;
-        ListMap<K, V> ret = null;
-        ListMap<K, V> lastN = null;
-        while (n != null) {
-            if (key.equals(n.k)) {
-                n = n.next;
-                continue;
-            }
-
-            if (ret != null) {
-                lastN.next = new ListMap<>(n.k, n.v);
-                lastN = lastN.next;
-            } else {
-                ret = new ListMap<>(n.k, n.v);
-                lastN = ret;
-            }
-            n = n.next;
-        }
-        return ret;
-    }
-
-    Iterator<Entry<K, V>> iterator() {
-        return new NodeIterator<>(this);
-    }
-
-    static final class NodeIterator<K,V> implements Iterator<Entry<K, V>> {
-        private ListMap<K, V> n;
-
-        NodeIterator(final ListMap<K, V> n) {
-            this.n = n;
-        }
-
-        @Override
-        public boolean hasNext() {
-            return n != null;
-        }
-
-        @Override
-        public Entry<K, V> next() {
-            if (n == null) {
-                throw new NoSuchElementException();
-            }
-
-            final Entry<K, V> res = new SimpleImmutableEntry<>(n.k, n.v);
-            n = n.next;
-            return res;
-        }
-    }
-}
index b9071d480d3e10d99c1f49c4a65330819d468bd6..0b2b8987919f9bba75ee12e596b83334a39c68b8 100644 (file)
@@ -43,8 +43,8 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  *
  * @author Roman Levenstein &lt;romixlev@gmail.com&gt;
  *
- * @param <K>
- * @param <V>
+ * @param <K> the type of keys maintained by this map
+ * @param <V> the type of mapped values
  */
 @SuppressWarnings({"unchecked", "rawtypes", "unused"})
 public final class TrieMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K,V>, Serializable {
index ffecafd9e9fc2db371e1518d0602b58908abc4f1..0786ce852e676cfdbb8b18ff74d2fed5af079c36 100644 (file)
  */
 package org.opendaylight.yangtools.triemap;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
-import java.util.Optional;
 import org.junit.Test;
 
 public class ListMapTest {
-
     /**
      * Test if Listmap.get() does not cause stack overflow.
      */
     @Test
     public void testGetOverflow() {
-        ListMap<Integer, Boolean> map = ListMap.map(1, Boolean.TRUE, 2, Boolean.TRUE);
+        LNodeEntries<Integer, Boolean> map = LNodeEntries.map(1, Boolean.TRUE, 2, Boolean.TRUE);
 
         // 30K seems to be enough to trigger the problem locally
         for (int i = 3; i < 30000; ++i) {
-            map = map.add(Equivalence.equals(), i, Boolean.TRUE);
+            map = map.insert(i, Boolean.TRUE);
         }
 
-        final Optional<Boolean> ret = map.get(Equivalence.equals(), 0);
-        assertFalse(ret.isPresent());
+        assertNull(map.findEntry(Equivalence.equals(), 0));
     }
-
 }