Optimize SubstatementContext size 67/87267/1
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 24 Jan 2020 14:11:01 +0000 (15:11 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 29 Jan 2020 16:14:05 +0000 (17:14 +0100)
Class layout of SubstatementContext contains a set of flags in
StatementContextBase and SubstatementContext, both of which end up
being padded -- wasting 2-10 bytes in the padding alone.

Moving configuration/ignoreConfig/ignoreIfFeature to
StatementContextBase and allocating them as individual bits allows
us to eliminate internal losses in most cases, resulting in net
savings of 8 bytes per instance in the common case (64bit VM), i.e.
4.5-8.3%.

JIRA: YANGTOOLS-652
Change-Id: Ic63f75e9e8c1c25445bc7904c08a5691d1470b67
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 79244c9c657423d5cf853952e6e00d1f687749ea)

yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SubstatementContext.java

index b6c87982690589df9c86a2208a8321bee3a5203e..93540bda86a8b36f276274da889807cbc75600e1 100644 (file)
@@ -34,7 +34,6 @@ import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.util.OptionalBoolean;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
@@ -43,9 +42,11 @@ import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
+import org.opendaylight.yangtools.yang.model.api.stmt.ConfigStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyHistory;
 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ImplicitParentAwareStatementSupport;
+import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
@@ -98,6 +99,22 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     private static final Logger LOG = LoggerFactory.getLogger(StatementContextBase.class);
 
+    // Flag bit assignments
+    private static final int IS_SUPPORTED_BY_FEATURES    = 0x01;
+    private static final int HAVE_SUPPORTED_BY_FEATURES  = 0x02;
+    private static final int IS_CONFIGURATION            = 0x04;
+    private static final int HAVE_CONFIGURATION          = 0x08;
+    private static final int IS_IGNORE_CONFIG            = 0x10;
+    private static final int HAVE_IGNORE_CONFIG          = 0x20;
+    private static final int IS_IGNORE_IF_FEATURE        = 0x40;
+    private static final int HAVE_IGNORE_IF_FEATURE      = 0x80;
+
+    // Have-and-set flag constants, also used as masks
+    private static final int SET_SUPPORTED_BY_FEATURES = HAVE_SUPPORTED_BY_FEATURES | IS_SUPPORTED_BY_FEATURES;
+    private static final int SET_CONFIGURATION = HAVE_CONFIGURATION | IS_CONFIGURATION;
+    private static final int SET_IGNORE_CONFIG = HAVE_IGNORE_CONFIG | IS_IGNORE_CONFIG;
+    private static final int SET_IGNORE_IF_FEATURE = HAVE_IGNORE_IF_FEATURE | IS_IGNORE_IF_FEATURE;
+
     private final @NonNull StatementDefinitionContext<A, D, E> definition;
     private final @NonNull StatementSourceReference statementDeclSource;
     private final StmtContext<?, ?, ?> originalCtx;
@@ -111,16 +128,18 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     private List<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
     private StatementMap substatements = StatementMap.empty();
 
-    private boolean isSupportedToBuildEffective = true;
     private @Nullable ModelProcessingPhase completedPhase;
     private @Nullable D declaredInstance;
     private @Nullable E effectiveInstance;
 
-    // BooleanFields value
-    private byte supportedByFeatures;
-
+    // Common state bits
+    private boolean isSupportedToBuildEffective = true;
     private boolean fullyDefined;
 
+    // Flags for use with SubstatementContext. These are hiding in the alignment shadow created by above booleans and
+    // hence improve memory layout.
+    private byte flags;
+
     StatementContextBase(final StatementDefinitionContext<A, D, E> def, final StatementSourceReference ref,
             final String rawArgument) {
         this.definition = requireNonNull(def);
@@ -188,44 +207,35 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     @Override
     public boolean isSupportedByFeatures() {
-        if (OptionalBoolean.isPresent(supportedByFeatures)) {
-            return OptionalBoolean.get(supportedByFeatures);
+        final int fl = flags & SET_SUPPORTED_BY_FEATURES;
+        if (fl != 0) {
+            return fl == SET_SUPPORTED_BY_FEATURES;
         }
-
         if (isIgnoringIfFeatures()) {
-            supportedByFeatures = OptionalBoolean.of(true);
+            flags |= SET_SUPPORTED_BY_FEATURES;
             return true;
         }
 
-        final boolean isParentSupported = isParentSupportedByFeatures();
         /*
-         * If parent is not supported, then this context is also not supported.
-         * So we do not need to check if-features statements of this context and
-         * we can return false immediately.
+         * If parent is supported, we need to check if-features statements of this context.
          */
-        if (!isParentSupported) {
-            supportedByFeatures = OptionalBoolean.of(false);
-            return false;
+        if (isParentSupportedByFeatures()) {
+            // If the set of supported features has not been provided, all features are supported by default.
+            final Set<QName> supportedFeatures = getFromNamespace(SupportedFeaturesNamespace.class,
+                    SupportedFeatures.SUPPORTED_FEATURES);
+            if (supportedFeatures == null || StmtContextUtils.checkFeatureSupport(this, supportedFeatures)) {
+                flags |= SET_SUPPORTED_BY_FEATURES;
+                return true;
+            }
         }
 
-        /*
-         * If parent is supported, we need to check if-features statements of
-         * this context.
-         */
-        // If the set of supported features has not been provided, all features are supported by default.
-        final Set<QName> supportedFeatures = getFromNamespace(SupportedFeaturesNamespace.class,
-                SupportedFeatures.SUPPORTED_FEATURES);
-        final boolean ret = supportedFeatures == null || StmtContextUtils.checkFeatureSupport(this, supportedFeatures);
-        supportedByFeatures = OptionalBoolean.of(ret);
-        return ret;
+        // Either parent is not supported or this statement is not supported
+        flags |= HAVE_SUPPORTED_BY_FEATURES;
+        return false;
     }
 
     protected abstract boolean isParentSupportedByFeatures();
 
-    protected abstract boolean isIgnoringIfFeatures();
-
-    protected abstract boolean isIgnoringConfig();
-
     @Override
     public boolean isSupportedToBuildEffective() {
         return isSupportedToBuildEffective;
@@ -844,6 +854,86 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return result;
     }
 
+    /**
+     * Config statements are not all that common which means we are performing a recursive search towards the root
+     * every time {@link #isConfiguration()} is invoked. This is quite expensive because it causes a linear search
+     * for the (usually non-existent) config statement.
+     *
+     * <p>
+     * This method maintains a resolution cache, so once we have returned a result, we will keep on returning the same
+     * result without performing any lookups, solely to support {@link SubstatementContext#isConfiguration()}.
+     */
+    final boolean isConfiguration(final StatementContextBase<?, ?, ?> parent) {
+        if (isIgnoringConfig()) {
+            return true;
+        }
+        final int fl = flags & SET_CONFIGURATION;
+        if (fl != 0) {
+            return fl == SET_CONFIGURATION;
+        }
+        final StmtContext<Boolean, ?, ?> configStatement = StmtContextUtils.findFirstSubstatement(this,
+            ConfigStatement.class);
+        final boolean parentIsConfig = parent.isConfiguration();
+
+        final boolean isConfig;
+        if (configStatement != null) {
+            isConfig = configStatement.coerceStatementArgument();
+
+            // Validity check: if parent is config=false this cannot be a config=true
+            InferenceException.throwIf(isConfig && !parentIsConfig, getStatementSourceReference(),
+                    "Parent node has config=false, this node must not be specifed as config=true");
+        } else {
+            // If "config" statement is not specified, the default is the same as the parent's "config" value.
+            isConfig = parentIsConfig;
+        }
+
+        // Resolved, make sure we cache this return
+        flags |= isConfig ? SET_CONFIGURATION : HAVE_CONFIGURATION;
+        return isConfig;
+    }
+
+    protected abstract boolean isIgnoringConfig();
+
+    /**
+     * This method maintains a resolution cache for ignore config, so once we have returned a result, we will
+     * keep on returning the same result without performing any lookups. Exists only to support
+     * {@link SubstatementContext#isIgnoringConfig()}.
+     */
+    final boolean isIgnoringConfig(final StatementContextBase<?, ?, ?> parent) {
+        final int fl = flags & SET_IGNORE_CONFIG;
+        if (fl != 0) {
+            return fl == SET_IGNORE_CONFIG;
+        }
+        if (definition().isIgnoringConfig() || parent.isIgnoringConfig()) {
+            flags |= SET_IGNORE_CONFIG;
+            return true;
+        }
+
+        flags |= HAVE_IGNORE_CONFIG;
+        return false;
+    }
+
+    protected abstract boolean isIgnoringIfFeatures();
+
+    /**
+     * This method maintains a resolution cache for ignore if-feature, so once we have returned a result, we will
+     * keep on returning the same result without performing any lookups. Exists only to support
+     * {@link SubstatementContext#isIgnoringIfFeatures()}.
+     */
+    final boolean isIgnoringIfFeatures(final StatementContextBase<?, ?, ?> parent) {
+        final int fl = flags & SET_IGNORE_IF_FEATURE;
+        if (fl != 0) {
+            return fl == SET_IGNORE_IF_FEATURE;
+        }
+        if (definition().isIgnoringIfFeatures() || parent.isIgnoringIfFeatures()) {
+            flags |= SET_IGNORE_IF_FEATURE;
+            return true;
+        }
+
+        flags |= HAVE_IGNORE_IF_FEATURE;
+        return false;
+    }
+
     final void copyTo(final StatementContextBase<?, ?, ?> target, final CopyType typeOfCopy,
             @Nullable final QNameModule targetModule) {
         final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(substatements.size() + effective.size());
index e70a5841cafad8ff3c726bd7c50dcfe53b89a536..205fb36fdbddec9e0d89f6d0f01ae43e54c20d66 100644 (file)
@@ -11,7 +11,6 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Verify;
 import java.util.Optional;
-import org.opendaylight.yangtools.util.OptionalBoolean;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.YangVersion;
@@ -19,14 +18,12 @@ import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ConfigStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.DeviationStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement;
 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.InferenceException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.NamespaceStorageNode;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Registry;
@@ -40,32 +37,6 @@ final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends Eff
     private final StatementContextBase<?, ?, ?> parent;
     private final A argument;
 
-    /**
-     * Config statements are not all that common which means we are performing a recursive search towards the root
-     * every time {@link #isConfiguration()} is invoked. This is quite expensive because it causes a linear search
-     * for the (usually non-existent) config statement.
-     *
-     * <p>
-     * This field maintains a resolution cache, so once we have returned a result, we will keep on returning the same
-     * result without performing any lookups.
-     */
-    // BooleanField value
-    private byte configuration;
-
-    /**
-     * This field maintains a resolution cache for ignore config, so once we have returned a result, we will
-     * keep on returning the same result without performing any lookups.
-     */
-    // BooleanField value
-    private byte ignoreConfig;
-
-    /**
-     * This field maintains a resolution cache for ignore if-feature, so once we have returned a result, we will
-     * keep on returning the same result without performing any lookups.
-     */
-    // BooleanField value
-    private byte ignoreIfFeature;
-
     private volatile SchemaPath schemaPath;
 
     SubstatementContext(final StatementContextBase<?, ?, ?> parent, final StatementDefinitionContext<A, D, E> def,
@@ -178,33 +149,7 @@ final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends Eff
 
     @Override
     public boolean isConfiguration() {
-        if (isIgnoringConfig()) {
-            return true;
-        }
-
-        if (OptionalBoolean.isPresent(configuration)) {
-            return OptionalBoolean.get(configuration);
-        }
-
-        final StmtContext<Boolean, ?, ?> configStatement = StmtContextUtils.findFirstSubstatement(this,
-            ConfigStatement.class);
-        final boolean parentIsConfig = parent.isConfiguration();
-
-        final boolean isConfig;
-        if (configStatement != null) {
-            isConfig = configStatement.coerceStatementArgument();
-
-            // Validity check: if parent is config=false this cannot be a config=true
-            InferenceException.throwIf(isConfig && !parentIsConfig, getStatementSourceReference(),
-                    "Parent node has config=false, this node must not be specifed as config=true");
-        } else {
-            // If "config" statement is not specified, the default is the same as the parent's "config" value.
-            isConfig = parentIsConfig;
-        }
-
-        // Resolved, make sure we cache this return
-        configuration = OptionalBoolean.of(isConfig);
-        return isConfig;
+        return isConfiguration(parent);
     }
 
     @Override
@@ -239,26 +184,12 @@ final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends Eff
 
     @Override
     protected boolean isIgnoringIfFeatures() {
-        if (OptionalBoolean.isPresent(ignoreIfFeature)) {
-            return OptionalBoolean.get(ignoreIfFeature);
-        }
-
-        final boolean ret = definition().isIgnoringIfFeatures() || parent.isIgnoringIfFeatures();
-        ignoreIfFeature = OptionalBoolean.of(ret);
-
-        return ret;
+        return isIgnoringIfFeatures(parent);
     }
 
     @Override
     protected boolean isIgnoringConfig() {
-        if (OptionalBoolean.isPresent(ignoreConfig)) {
-            return OptionalBoolean.get(ignoreConfig);
-        }
-
-        final boolean ret = definition().isIgnoringConfig() || parent.isIgnoringConfig();
-        ignoreConfig = OptionalBoolean.of(ret);
-
-        return ret;
+        return isIgnoringConfig(parent);
     }
 
     @Override