Use a dedicated map for schema/data tree namespace 54/98854/3
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 6 Dec 2021 09:50:52 +0000 (10:50 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 6 Dec 2021 11:20:31 +0000 (12:20 +0100)
Heap analysis shows that we have roughly 52K SingletonImmutableMaps,
each of which costs 48 bytes. We can store the equivalent information
with a dedicated structure.

JIRA: YANGTOOLS-652
Change-Id: I18d1f247fb6a03e6964efaca4a612917eb89a6c9
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/meta/AbstractDeclaredEffectiveStatement.java
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/meta/AbstractEffectiveStatement.java
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/meta/AbstractUndeclaredEffectiveStatement.java
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/meta/SingletonNamespace.java [new file with mode: 0644]

index 8847b96b7c1db3795c0186fbbb6305e12e309e83..fd16fa23ed174832e1b0d8046d908ae628a6f0b5 100644 (file)
@@ -12,7 +12,6 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
@@ -226,7 +225,7 @@ public abstract class AbstractDeclaredEffectiveStatement<A, D extends DeclaredSt
      */
     public abstract static class DefaultWithSchemaTree<A, D extends DeclaredStatement<A>,
             E extends SchemaTreeAwareEffectiveStatement<A, D>> extends WithSchemaTree<A, D, E> {
-        private final @NonNull ImmutableMap<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
+        private final @NonNull Map<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
         private final @NonNull Object substatements;
         private final @NonNull D declared;
 
@@ -234,7 +233,7 @@ public abstract class AbstractDeclaredEffectiveStatement<A, D extends DeclaredSt
                 final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
             this.declared = requireNonNull(declared);
             this.substatements = maskList(substatements);
-            this.schemaTree = ImmutableMap.copyOf(createSchemaTreeNamespace(substatements));
+            this.schemaTree = immutableNamespaceOf(createSchemaTreeNamespace(substatements));
         }
 
         protected DefaultWithSchemaTree(final DefaultWithSchemaTree<A, D, E> original) {
@@ -293,8 +292,8 @@ public abstract class AbstractDeclaredEffectiveStatement<A, D extends DeclaredSt
             }
         }
 
-        private final @NonNull ImmutableMap<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
-        private final @NonNull ImmutableMap<QName, DataTreeEffectiveStatement<?>> dataTree;
+        private final @NonNull Map<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
+        private final @NonNull Map<QName, DataTreeEffectiveStatement<?>> dataTree;
         private final @NonNull Object substatements;
         private final @NonNull D declared;
 
@@ -305,7 +304,7 @@ public abstract class AbstractDeclaredEffectiveStatement<A, D extends DeclaredSt
 
             // Note we call schema.values() so we do not retain them, as that is just pure memory overhead
             final Map<QName, SchemaTreeEffectiveStatement<?>> schema = createSchemaTreeNamespace(substatements);
-            this.schemaTree = ImmutableMap.copyOf(schema);
+            this.schemaTree = immutableNamespaceOf(schema);
             this.dataTree = createDataTreeNamespace(schema.values(), schemaTree);
         }
 
index 27ed8eaecf0546a28433b6ad9829484ca78c5234..71fb8974aeb2a603696e2cc18c6ecb53bb4ad200 100644 (file)
@@ -101,10 +101,10 @@ abstract class AbstractEffectiveStatement<A, D extends DeclaredStatement<A>>
         return schemaChildren;
     }
 
-    protected static @NonNull ImmutableMap<QName, DataTreeEffectiveStatement<?>> createDataTreeNamespace(
+    protected static @NonNull Map<QName, DataTreeEffectiveStatement<?>> createDataTreeNamespace(
             final Collection<SchemaTreeEffectiveStatement<?>> schemaTreeStatements,
             // Note: this dance is needed to not retain ImmutableMap$Values
-            final ImmutableMap<QName, SchemaTreeEffectiveStatement<?>> schemaTreeNamespace) {
+            final Map<QName, SchemaTreeEffectiveStatement<?>> schemaTreeNamespace) {
         final Map<QName, DataTreeEffectiveStatement<?>> dataChildren = new LinkedHashMap<>();
         boolean sameAsSchema = true;
 
@@ -116,7 +116,12 @@ abstract class AbstractEffectiveStatement<A, D extends DeclaredStatement<A>>
 
         // This is a mighty hack to lower memory usage: if we consumed all schema tree children as data nodes,
         // the two maps are equal and hence we can share the instance.
-        return sameAsSchema ? (ImmutableMap) schemaTreeNamespace : ImmutableMap.copyOf(dataChildren);
+        return sameAsSchema ? (Map) schemaTreeNamespace : immutableNamespaceOf(dataChildren);
+    }
+
+    protected static <T extends SchemaTreeEffectiveStatement<?>> @NonNull Map<QName, T> immutableNamespaceOf(
+            final Map<QName, T> map) {
+        return map.size() == 1 ? new SingletonNamespace<>(map.values().iterator().next()) : ImmutableMap.copyOf(map);
     }
 
     protected static @NonNull HashMap<QName, TypedefEffectiveStatement> createTypedefNamespace(
index c4ba858efd539a2cc707defa22e38acd66e3384d..b1270ff886b5f5f01ccea445af37d02703c547af 100644 (file)
@@ -12,7 +12,6 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
@@ -108,12 +107,12 @@ public abstract class AbstractUndeclaredEffectiveStatement<A, D extends Declared
      */
     public abstract static class DefaultWithSchemaTree<A, D extends DeclaredStatement<A>,
             E extends SchemaTreeAwareEffectiveStatement<A, D>> extends WithSchemaTree<A, D, E> {
-        private final @NonNull ImmutableMap<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
+        private final @NonNull Map<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
         private final @NonNull Object substatements;
 
         protected DefaultWithSchemaTree(final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
             this.substatements = maskList(substatements);
-            this.schemaTree = ImmutableMap.copyOf(createSchemaTreeNamespace(substatements));
+            this.schemaTree = immutableNamespaceOf(createSchemaTreeNamespace(substatements));
         }
 
         protected DefaultWithSchemaTree(final DefaultWithSchemaTree<A, D, E> original) {
@@ -142,13 +141,13 @@ public abstract class AbstractUndeclaredEffectiveStatement<A, D extends Declared
      */
     public abstract static class DefaultWithDataTree<A, D extends DeclaredStatement<A>,
             E extends DataTreeAwareEffectiveStatement<A, D>> extends WithDataTree<A, D, E> {
-        private final @NonNull ImmutableMap<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
-        private final @NonNull ImmutableMap<QName, DataTreeEffectiveStatement<?>> dataTree;
+        private final @NonNull Map<QName, SchemaTreeEffectiveStatement<?>> schemaTree;
+        private final @NonNull Map<QName, DataTreeEffectiveStatement<?>> dataTree;
         private final @NonNull Object substatements;
 
         protected DefaultWithDataTree(final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
             final Map<QName, SchemaTreeEffectiveStatement<?>> schema = createSchemaTreeNamespace(substatements);
-            this.schemaTree = ImmutableMap.copyOf(schema);
+            this.schemaTree = immutableNamespaceOf(schema);
             this.dataTree = createDataTreeNamespace(schema.values(), schemaTree);
             this.substatements = maskList(substatements);
         }
diff --git a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/meta/SingletonNamespace.java b/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/meta/SingletonNamespace.java
new file mode 100644 (file)
index 0000000..a6f9f12
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2021 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.spi.meta;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.stmt.NamespacedEffectiveStatement;
+
+final class SingletonNamespace<T extends NamespacedEffectiveStatement<?>> implements Map<QName, T> {
+    private final @NonNull T item;
+
+    SingletonNamespace(final T item) {
+        this.item = requireNonNull(item);
+    }
+
+    @Override
+    public int size() {
+        return 1;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    @Override
+    public boolean containsKey(final Object key) {
+        return key.equals(item.argument());
+    }
+
+    @Override
+    public boolean containsValue(final Object value) {
+        return value.equals(item);
+    }
+
+    @Override
+    public T get(final Object key) {
+        return containsKey(key) ? item : null;
+    }
+
+    @Override
+    public T put(final QName key, final T value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public T remove(final Object key) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public void putAll(final Map<? extends QName, ? extends T> m) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<QName> keySet() {
+        return Set.of(item.argument());
+    }
+
+    @Override
+    public Collection<T> values() {
+        return List.of(item);
+    }
+
+    @Override
+    public Set<Entry<QName, T>> entrySet() {
+        return Set.of(Map.entry(item.argument(), item));
+    }
+
+    @Override
+    public int hashCode() {
+        return item.getIdentifier().hashCode() ^ item.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof SingletonNamespace) {
+            return item.equals(((SingletonNamespace<?>) obj).item);
+        }
+        if (obj instanceof Map) {
+            final var it = ((Map<?, ?>) obj).entrySet().iterator();
+            if (it.hasNext()) {
+                final var entry = it.next();
+                if (!it.hasNext() && item.argument().equals(entry.getKey()) && item.equals(entry.getValue())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "{" + item.argument() + "=" + item + "}";
+    }
+}