Intern statements along copy axis 78/97378/7
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 2 Sep 2021 17:39:54 +0000 (19:39 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sat, 4 Dec 2021 08:16:07 +0000 (09:16 +0100)
When we are inferring statements through grouping/uses, we end up adding
a few bits to record the copy history and effective status/config. This
process does not feed back anything back to the original declaration
site.

This leads to a situation when we can have a number of inferred
statements which are exactly equal, yet each is its own java object --
hence unnecessarily using heap for duplicate objects.

We can improve this by having a deduplication map at the site of
original declaration and updating/consulting it when we find we have an
otherwise-unmodified statement.

This patch adds the prerequisite constructs to yang-parser-spi and
the smarts to perform this deduplication to yang-parser-reactor.

JIRA: YANGTOOLS-1214
Change-Id: I957c6d69d16717759f22a78163c705390e602e22
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/EffectiveInstances.java [new file with mode: 0644]
parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java
parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java
parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/EffectiveStatementState.java [new file with mode: 0644]
parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StatementFactory.java
parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StatementSupport.java

diff --git a/parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/EffectiveInstances.java b/parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/EffectiveInstances.java
new file mode 100644 (file)
index 0000000..62567aa
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.parser.stmt.reactor;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Mutable;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStatementState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Statement-reuse expansion of a single instance. The idea here is that a statement can end up being replicated the
+ * same way multiple times -- which does not typically happen, but when it does it is worth exploiting.
+ *
+ * @param <E> {@link EffectiveStatement} type
+ */
+final class EffectiveInstances<E extends EffectiveStatement<?, ?>> implements Mutable {
+    private static final Logger LOG = LoggerFactory.getLogger(EffectiveInstances.class);
+
+    // Note on sizing: this fits three entries without expansion. Note we do not include the local copy, as it does
+    // not have the same original.
+    private final Map<EffectiveStatementState, E> copies = new HashMap<>(4);
+    private final @NonNull E local;
+
+    EffectiveInstances(final E local) {
+        this.local = requireNonNull(local);
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E extends EffectiveStatement<?, ?>> @NonNull E local(final Object obj) {
+        return obj instanceof EffectiveInstances ? ((EffectiveInstances<E>) obj).local : requireNonNull((E) obj);
+    }
+
+    @NonNull E attachCopy(final @NonNull EffectiveStatementState key, @NonNull final E copy) {
+        final E prev = copies.putIfAbsent(requireNonNull(key), requireNonNull(copy));
+        if (prev == null) {
+            // Nothing matching state
+            return copy;
+        }
+
+        // We need to make sure substatements are actually the same. If they are not, we'll just return the copy,
+        // playing it safe.
+        final Collection<? extends EffectiveStatement<?, ?>> prevStmts = prev.effectiveSubstatements();
+        final Collection<? extends EffectiveStatement<?, ?>> copyStmts = copy.effectiveSubstatements();
+        if (prevStmts != copyStmts) {
+            if (prevStmts.size() != copyStmts.size()) {
+                LOG.trace("Key {} substatement count mismatch", key);
+                return copy;
+            }
+
+            final Iterator<? extends EffectiveStatement<?, ?>> prevIt = prevStmts.iterator();
+            final Iterator<? extends EffectiveStatement<?, ?>> copyIt = copyStmts.iterator();
+            while (prevIt.hasNext()) {
+                if (prevIt.next() != copyIt.next()) {
+                    LOG.trace("Key {} substatement mismatch", key);
+                    return copy;
+                }
+            }
+        }
+
+        LOG.trace("Key {} substatement reused", key);
+        return prev;
+    }
+}
index aff44fd619a8574db6c3f0f7b5696fa88fdee7d3..912e82bf43462d72bf677179b710d43a7e7a06c5 100644 (file)
@@ -34,6 +34,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
 import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
+import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStatementState;
 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.OnDemandSchemaTreeStorageNode;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
@@ -219,8 +220,7 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
 
         // First check if we can reuse the entire prototype
         if (!factory.canReuseCurrent(this, prototype, origSubstatements)) {
-            // FIXME: YANGTOOLS-1214: deduplicate this return
-            return tryToReuseSubstatements(factory, origEffective);
+            return internAlongCopyAxis(factory, tryToReuseSubstatements(factory, origEffective));
         }
 
         // We can reuse this statement let's see if all statements agree...
@@ -272,8 +272,7 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
         prototype.decRef();
 
         // Values are the effective copies, hence this efficiently deals with recursion.
-        // FIXME: YANGTOOLS-1214: deduplicate this return
-        return factory.createEffective(this, declared.stream(), effective.stream());
+        return internAlongCopyAxis(factory, factory.createEffective(this, declared.stream(), effective.stream()));
     }
 
     private @NonNull E tryToReuseSubstatements(final StatementFactory<A, D, E> factory, final @NonNull E original) {
@@ -297,6 +296,16 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
         return effective;
     }
 
+    private @NonNull E internAlongCopyAxis(final StatementFactory<A, D, E> factory, final @NonNull E stmt) {
+        if (!isModified()) {
+            final EffectiveStatementState state = factory.extractEffectiveState(stmt);
+            if (state != null) {
+                return prototype.unmodifiedEffectiveSource().attachEffectiveCopy(state, stmt);
+            }
+        }
+        return stmt;
+    }
+
     private List<ReactorStmtCtx<?, ?, ?>> reusePrototypeReplicas() {
         return reusePrototypeReplicas(Streams.concat(prototype.streamDeclared(), prototype.streamEffective()));
     }
index 7b9bea03e1db2a3ad593266aae4c8d73824be5b7..2e8e3250b8267da6d1c4f67045606d86e3681ed7 100644 (file)
@@ -34,6 +34,7 @@ import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
+import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStatementState;
 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
@@ -117,7 +118,7 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
      * {@link #buildEffective()} instance. If this context is reused, it can be inflated to {@link EffectiveInstances}
      * and also act as a common instance reuse site.
      */
-    private @Nullable E effectiveInstance;
+    private @Nullable Object effectiveInstance;
 
     // Master flag controlling whether this context can yield an effective statement
     // FIXME: investigate the mechanics that are being supported by this, as it would be beneficial if we can get rid
@@ -253,17 +254,22 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
     @Override
     public final <X, Z extends EffectiveStatement<X, ?>> @NonNull Optional<X> findSubstatementArgument(
             final @NonNull Class<Z> type) {
-        final E existing = effectiveInstance;
+        final E existing = effectiveInstance();
         return existing != null ? existing.findFirstEffectiveSubstatementArgument(type)
             : findSubstatementArgumentImpl(type);
     }
 
     @Override
     public final boolean hasSubstatement(final @NonNull Class<? extends EffectiveStatement<?, ?>> type) {
-        final E existing = effectiveInstance;
+        final E existing = effectiveInstance();
         return existing != null ? existing.findFirstEffectiveSubstatement(type).isPresent() : hasSubstatementImpl(type);
     }
 
+    private E effectiveInstance() {
+        final Object existing = effectiveInstance;
+        return existing != null ? EffectiveInstances.local(existing) : null;
+    }
+
     // Visible due to InferredStatementContext's override. At this point we do not have an effective instance available.
     <X, Z extends EffectiveStatement<X, ?>> @NonNull Optional<X> findSubstatementArgumentImpl(
             final @NonNull Class<Z> type) {
@@ -378,8 +384,8 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
 
     @Override
     public final E buildEffective() {
-        final E existing;
-        return (existing = effectiveInstance) != null ? existing : loadEffective();
+        final Object existing;
+        return (existing = effectiveInstance) != null ? EffectiveInstances.local(existing) : loadEffective();
     }
 
     private @NonNull E loadEffective() {
@@ -401,6 +407,29 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
 
     abstract @NonNull E createEffective();
 
+    /**
+     * Attach an effective copy of this statement. This essentially acts as a map, where we make a few assumptions:
+     * <ul>
+     *   <li>{@code copy} and {@code this} statement share {@link #getOriginalCtx()} if it exists</li>
+     *   <li>{@code copy} did not modify any statements relative to {@code this}</li>
+     * </ul>
+     *
+     * @param state effective statement state, acting as a lookup key
+     * @param stmt New copy to append
+     * @return {@code stmt} or a previously-created instances with the same {@code state}
+     */
+    @SuppressWarnings("unchecked")
+    final @NonNull E attachEffectiveCopy(final @NonNull EffectiveStatementState state, final @NonNull E stmt) {
+        final Object local = effectiveInstance;
+        final EffectiveInstances<E> instances;
+        if (local instanceof EffectiveInstances) {
+            instances = (EffectiveInstances<E>) local;
+        } else {
+            effectiveInstance = instances = new EffectiveInstances<>((E) local);
+        }
+        return instances.attachCopy(state, stmt);
+    }
+
     /**
      * Walk this statement's copy history and return the statement closest to original which has not had its effective
      * statements modified. This statement and returned substatement logically have the same set of substatements, hence
diff --git a/parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/EffectiveStatementState.java b/parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/EffectiveStatementState.java
new file mode 100644 (file)
index 0000000..0a0cc2b
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.parser.spi.meta;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+
+/**
+ * Significant state captured by an {@link EffectiveStatement} at its instantiation site. This can be used to compare
+ * statements which are instantiated through inference, for example through {@code uses} from {@code grouping}s, when
+ * the reactor establishes the two instances have the same substatements.
+ */
+@Beta
+public abstract class EffectiveStatementState implements Immutable {
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object obj);
+
+    @Override
+    public final String toString() {
+        return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
+    }
+
+    protected abstract ToStringHelper addToStringAttributes(ToStringHelper helper);
+}
index 777227f45c33939ff0efe579ab4b5bb8005ece0a..f860cffd597fc730cd22840680bed88190e7b60b 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.parser.spi.meta;
 import java.util.Collection;
 import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
@@ -72,4 +73,14 @@ public interface StatementFactory<A, D extends DeclaredStatement<A>, E extends E
      */
     boolean canReuseCurrent(@NonNull Current<A, D> copy, @NonNull Current<A, D> current,
         @NonNull Collection<? extends EffectiveStatement<?, ?>> substatements);
+
+    /**
+     * Return the {@link EffectiveStatementState} for a particular statement. This acts as a summary for comparison with
+     * statements created by this factory, without taking substatements into account. This is an optional operation, it
+     * is always safe to return null.
+     *
+     * @param stmt EffectiveStatement to examine
+     * @return EffectiveStatementState or null if the statement cannot be expressed
+     */
+    @Nullable EffectiveStatementState extractEffectiveState(@NonNull E stmt);
 }
index 0db910ffaf15505e09b0fbd35900aad05b493ca3..340f3224e865eabd666f670517958a5c5070709c 100644 (file)
@@ -271,6 +271,12 @@ public abstract class StatementSupport<A, D extends DeclaredStatement<A>, E exte
         return policy.canReuseCurrent(copy, current, substatements);
     }
 
+    @Override
+    public EffectiveStatementState extractEffectiveState(final E stmt) {
+        // Not implemented for most statements
+        return null;
+    }
+
     /**
      * Parses textual representation of argument in object representation.
      *