Do not cache path from root 83/87583/5
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 11 Feb 2020 14:09:49 +0000 (15:09 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 11 Feb 2020 17:40:01 +0000 (18:40 +0100)
Heap analysis shows that full 16% of SchemaContext memory is retained
by SchemaPath instances. We can reduce this by removing path caching,
so that each query to getPathFromRoot() results in a new collection.

This saves a pointer field in the structure, saving 8 bytes in typical
64bit JVM scenarios -- going from 32/48 to 24/40 bytes, saving 16-25%
instance size.

JIRA: YANGTOOLS-1076
Change-Id: I5613764c513b9b54473e2e587de2b8b38713ed15
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/PathFromRoot.java [new file with mode: 0644]
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/SchemaPath.java

diff --git a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/PathFromRoot.java b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/PathFromRoot.java
new file mode 100644 (file)
index 0000000..ca30dd0
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.model.api;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.AbstractList;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.Spliterator;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.common.QName;
+
+final class PathFromRoot extends AbstractList<QName> implements Immutable {
+    private static final QName[] EMPTY_QNAMES = new QName[0];
+    private static final VarHandle QNAMES;
+
+    static {
+        try {
+            QNAMES = MethodHandles.lookup().findVarHandle(PathFromRoot.class, "qnames", QName[].class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private final SchemaPath path;
+
+    @SuppressWarnings("unused")
+    private QName @Nullable [] qnames;
+
+    PathFromRoot(final SchemaPath path) {
+        this.path = requireNonNull(path);
+    }
+
+    @Override
+    public Iterator<QName> iterator() {
+        return Arrays.asList(qnames()).iterator();
+    }
+
+    @Override
+    public Spliterator<QName> spliterator() {
+        return Arrays.spliterator(qnames());
+    }
+
+    @Override
+    public QName get(final int index) {
+        return qnames()[index];
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    @Override
+    public int size() {
+        return qnames().length;
+    }
+
+    private QName @NonNull [] qnames() {
+        final QName[] local = (QName[]) QNAMES.getAcquire(this);
+        return local != null ? local : loadQNames();
+    }
+
+    private QName @NonNull [] loadQNames() {
+        final Deque<QName> tmp = new ArrayDeque<>();
+        for (QName qname : path.getPathTowardsRoot()) {
+            tmp.addFirst(qname);
+        }
+
+        final QName[] result = tmp.toArray(EMPTY_QNAMES);
+        // We do not care about atomicity here
+        QNAMES.setRelease(this, result);
+        return result;
+    }
+}
index 7d6ca30e48dbd808e49b8f1ba727a9121957faa3..2414d78fb64909bb57550a9d78e46f52bcdd0a7d 100644 (file)
@@ -14,13 +14,8 @@ import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.collect.UnmodifiableIterator;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.VarHandle;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import org.eclipse.jdt.annotation.NonNull;
@@ -72,16 +67,6 @@ public abstract class SchemaPath implements Immutable {
         }
     }
 
-    private static final VarHandle LEGACY_PATH;
-
-    static {
-        try {
-            LEGACY_PATH = MethodHandles.lookup().findVarHandle(SchemaPath.class, "legacyPath", ImmutableList.class);
-        } catch (NoSuchFieldException | IllegalAccessException e) {
-            throw new ExceptionInInitializerError(e);
-        }
-    }
-
     /**
      * Shared instance of the conceptual root schema node.
      */
@@ -107,12 +92,6 @@ public abstract class SchemaPath implements Immutable {
      */
     private final int hash;
 
-    /**
-     * Cached legacy path, filled-in when {@link #getPath()} or {@link #getPathTowardsRoot()} is invoked.
-     */
-    @SuppressWarnings("unused")
-    private volatile ImmutableList<QName> legacyPath;
-
     SchemaPath(final SchemaPath parent, final QName qname) {
         this.parent = parent;
         this.qname = qname;
@@ -125,22 +104,6 @@ public abstract class SchemaPath implements Immutable {
         hash = tmp;
     }
 
-    private ImmutableList<QName> getLegacyPath() {
-        final ImmutableList<QName> local = (ImmutableList<QName>) LEGACY_PATH.getAcquire(this);
-        return local != null ? local : loadLegacyPath();
-    }
-
-    @SuppressWarnings("unchecked")
-    private ImmutableList<QName> loadLegacyPath() {
-        final List<QName> tmp = new ArrayList<>();
-        for (QName item : getPathTowardsRoot()) {
-            tmp.add(item);
-        }
-        final ImmutableList<QName> ret = ImmutableList.copyOf(Lists.reverse(tmp));
-        final Object witness = LEGACY_PATH.compareAndExchangeRelease(this, null, ret);
-        return witness == null ? ret : (ImmutableList<QName>) witness;
-    }
-
     /**
      * Constructs new instance of this class with the concrete path.
      *
@@ -247,7 +210,10 @@ public abstract class SchemaPath implements Immutable {
      *         path from the root to the schema node.
      */
     public Iterable<QName> getPathFromRoot() {
-        return getLegacyPath();
+        if (qname == null) {
+            return ImmutableList.of();
+        }
+        return parent == null ? ImmutableList.of(qname) : new PathFromRoot(this);
     }
 
     /**