Add a specialized token factory 07/92007/16
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 10 Aug 2020 12:07:00 +0000 (14:07 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 20 Aug 2020 07:42:40 +0000 (09:42 +0200)
Our baseline parsing footprint is rather large. We can improve it
by looking at token component sizes and using distinct token classes.

Aside from CommonToken, which are using as fallbacks for text
handling, we introduce 4 specialized classes, three of them seeing
typical use.

Since these classes use smaller fields to hold lazy state, as well
as eliminate typical invariants, we end up saving around 39MiB (12%)
of AST size in a typical case from the field.

JIRA: YANGTOOLS-1128
Change-Id: I600ae6cbe755212e2d1a15ae3e6b369ed0b3b962
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/AbstractSourceToken.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/AbstractToken.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/CompactTokenFactory.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/CompactYangStatementLexer.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/ExplicitTextToken.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token12122.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token12144.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token44444.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangStatementStreamSource.java

diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/AbstractSourceToken.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/AbstractSourceToken.java
new file mode 100644 (file)
index 0000000..fc2bdc8
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import static java.util.Objects.requireNonNull;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.TokenSource;
+import org.antlr.v4.runtime.misc.Interval;
+import org.antlr.v4.runtime.misc.Pair;
+
+abstract class AbstractSourceToken extends AbstractToken {
+    private final Pair<TokenSource, CharStream> source;
+
+    AbstractSourceToken(final Pair<TokenSource, CharStream> source) {
+        this.source = requireNonNull(source);
+    }
+
+    @Override
+    public final TokenSource getTokenSource() {
+        return source.a;
+    }
+
+    @Override
+    public final CharStream getInputStream() {
+        return source.b;
+    }
+
+    @Override
+    public final String getText() {
+        final CharStream input = getInputStream();
+        if (input == null) {
+            return null;
+        }
+
+        final int n = input.size();
+        final int start = getStartIndex();
+        final int stop = getStopIndex();
+        return start < n && stop < n ? input.getText(Interval.of(start, stop)) : "<EOF>";
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/AbstractToken.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/AbstractToken.java
new file mode 100644 (file)
index 0000000..ced48bd
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import org.antlr.v4.runtime.WritableToken;
+
+abstract class AbstractToken implements WritableToken {
+    private int tokenIndex = -1;
+
+    @Override
+    public final int getChannel() {
+        return DEFAULT_CHANNEL;
+    }
+
+    @Override
+    public final int getTokenIndex() {
+        return tokenIndex;
+    }
+
+    @Override
+    public final void setTokenIndex(final int index) {
+        checkState(tokenIndex == -1);
+        tokenIndex = index;
+    }
+
+    @Override
+    public final void setText(final String text) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void setType(final int ttype) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void setLine(final int line) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void setCharPositionInLine(final int pos) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void setChannel(final int channel) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/CompactTokenFactory.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/CompactTokenFactory.java
new file mode 100644 (file)
index 0000000..598be06
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CommonTokenFactory;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenFactory;
+import org.antlr.v4.runtime.TokenSource;
+import org.antlr.v4.runtime.misc.Pair;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * A token factory which is more memory-efficient than {@link CommonTokenFactory}. We try to mimic behavior of common
+ * tokens, but do so at lower memory overheads, potentially sacrificing some performance.
+ */
+final class CompactTokenFactory implements TokenFactory<Token> {
+    static final @NonNull CompactTokenFactory INSTANCE = new CompactTokenFactory();
+
+    private CompactTokenFactory() {
+        // Hidden on purpose
+    }
+
+    @Override
+    public Token create(final Pair<TokenSource, CharStream> source, final int type, final String text,
+            final int channel, final int start, final int stop, final int line, final int charPositionInLine) {
+        if (channel != Token.DEFAULT_CHANNEL || text != null) {
+            // Non-default channel or text present, defer to common token factory
+            return CommonTokenFactory.DEFAULT.create(source, type, text, channel, start, stop, line,
+                charPositionInLine);
+        }
+
+        // Can we fit token type into a single byte? This should always be true
+        if (type >= Byte.MIN_VALUE && type <= Byte.MAX_VALUE) {
+            // Can we fit line in an unsigned short? This is usually be true
+            if (line >= 0 && line <= 65535) {
+                // Can we fit position in line into an unsigned byte? This is usually true
+                if (charPositionInLine >= 0 && charPositionInLine <= 255) {
+                    // Can we fit start/stop into an an unsigned short?
+                    if (start >= 0 && start <= 65535 && stop >= 0 && stop <= 65535) {
+                        return new Token12122(source, type, line, charPositionInLine, start, stop);
+                    }
+                    return new Token12144(source, type, line, charPositionInLine, start, stop);
+                }
+            }
+        }
+
+        return new Token44444(source, type, line, charPositionInLine, start, stop);
+    }
+
+    @Override
+    public Token create(final int type, final String text) {
+        return new ExplicitTextToken(type, text);
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/CompactYangStatementLexer.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/CompactYangStatementLexer.java
new file mode 100644 (file)
index 0000000..cd9447e
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import com.google.common.annotations.Beta;
+import org.antlr.v4.runtime.CharStream;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementLexer;
+
+/**
+ * A {@link YangStatementLexer} backed by more efficient token factory. Exact details are explicitly outside of
+ * specification.
+ */
+@Beta
+public class CompactYangStatementLexer extends YangStatementLexer {
+    public CompactYangStatementLexer(final CharStream input) {
+        super(input);
+        setTokenFactory(CompactTokenFactory.INSTANCE);
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/ExplicitTextToken.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/ExplicitTextToken.java
new file mode 100644 (file)
index 0000000..33f8ce1
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import static java.util.Objects.requireNonNull;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.TokenSource;
+
+final class ExplicitTextToken extends AbstractToken {
+    private final int type;
+    private final String text;
+
+    ExplicitTextToken(final int type, final String text) {
+        this.type = type;
+        this.text = requireNonNull(text);
+    }
+
+    @Override
+    public String getText() {
+        return text;
+    }
+
+    @Override
+    public int getType() {
+        return type;
+    }
+
+    @Override
+    public int getLine() {
+        // TODO: this mimics CommonToken, but is probably not right
+        return 0;
+    }
+
+    @Override
+    public int getCharPositionInLine() {
+        return -1;
+    }
+
+    @Override
+    public int getStartIndex() {
+        // TODO: this mimics CommonToken, but is probably not right
+        return 0;
+    }
+
+    @Override
+    public int getStopIndex() {
+        // TODO: this mimics CommonToken, but is probably not right
+        return 0;
+    }
+
+    @Override
+    public TokenSource getTokenSource() {
+        return null;
+    }
+
+    @Override
+    public CharStream getInputStream() {
+        return null;
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token12122.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token12122.java
new file mode 100644 (file)
index 0000000..789d33c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.TokenSource;
+import org.antlr.v4.runtime.misc.Pair;
+
+/**
+ * Smallest general token implementation. This cuts {@code channel}, {@code text} fields completely, as we typically
+ * we do not use them. {@code type} and {@code charPositionInLine} are cut down to a single byte, while others are cut
+ * down to unsigned shorts.
+ *
+ * <p>
+ * This class ends up costing 24/40/32/32 bytes instead of 48/64/48/48 bytes, a saving of 33-50%, while being sufficient
+ * in most scenarios.
+ */
+final class Token12122 extends AbstractSourceToken {
+    private final byte type;
+    private final short line;
+    private final byte charPositionInLine;
+    private final short startIndex;
+    private final short stopIndex;
+
+    Token12122(final Pair<TokenSource, CharStream> source, final int type, final int line, final int charPositionInLine,
+            final int startIndex, final int stopIndex) {
+        super(source);
+        this.type = (byte) type;
+        this.line = (short) line;
+        this.charPositionInLine = (byte) charPositionInLine;
+        this.startIndex = (short) startIndex;
+        this.stopIndex = (short) stopIndex;
+    }
+
+    @Override
+    public int getType() {
+        return type;
+    }
+
+    @Override
+    public int getLine() {
+        return Short.toUnsignedInt(line);
+    }
+
+    @Override
+    public int getCharPositionInLine() {
+        return Byte.toUnsignedInt(charPositionInLine);
+    }
+
+    @Override
+    public int getStartIndex() {
+        return Short.toUnsignedInt(startIndex);
+    }
+
+    @Override
+    public int getStopIndex() {
+        return Short.toUnsignedInt(stopIndex);
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token12144.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token12144.java
new file mode 100644 (file)
index 0000000..29dc881
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.TokenSource;
+import org.antlr.v4.runtime.misc.Pair;
+
+/**
+ * Intermediate general token implementation. This cuts {@code channel}, {@code text} fields completely, as we typically
+ * we do not use them. {@code type} and {@code charPositionInLine} are cut down to a single byte, and {@code line} is
+ * cut down to an unsigned short. {@code startIndex} and {@code stopIndex} are kept at full four-byte range, this making
+ * this implementation useful beyond 64K-char file mark.
+ *
+ * <p>
+ * This class ends up costing 32/48/32/32 bytes instead of 48/64/48/48 bytes, a saving of 33% in the same scenarios as
+ * {@link Token12122} across all possible file sizes.
+ */
+final class Token12144 extends AbstractSourceToken {
+    private final byte type;
+    private final short line;
+    private final byte charPositionInLine;
+
+    private final int startIndex;
+    private final int stopIndex;
+
+    Token12144(final Pair<TokenSource, CharStream> source, final int type, final int line, final int charPositionInLine,
+            final int startIndex, final int stopIndex) {
+        super(source);
+        this.type = (byte) type;
+        this.line = (short) line;
+        this.charPositionInLine = (byte) charPositionInLine;
+        this.startIndex = startIndex;
+        this.stopIndex = stopIndex;
+    }
+
+    @Override
+    public int getType() {
+        return type;
+    }
+
+    @Override
+    public int getLine() {
+        return Short.toUnsignedInt(line);
+    }
+
+    @Override
+    public int getCharPositionInLine() {
+        return Byte.toUnsignedInt(charPositionInLine);
+    }
+
+    @Override
+    public int getStartIndex() {
+        return startIndex;
+    }
+
+    @Override
+    public int getStopIndex() {
+        return stopIndex;
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token44444.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/antlr/Token44444.java
new file mode 100644 (file)
index 0000000..ecd4a6a
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.parser.rfc7950.antlr;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.TokenSource;
+import org.antlr.v4.runtime.misc.Pair;
+
+/**
+ * Large general token implementation. This cuts {@code channel}, {@code text} fields completely, as we typically
+ * we do not use them. All other fields are retained..
+ *
+ * <p>
+ * This class ends up costing 40/56/40/48 bytes instead of 48/64/48/48 bytes, a saving of 12-33%, while still being
+ * applicable in all situations.
+ */
+final class Token44444 extends AbstractSourceToken {
+    private final int type;
+    private final int line;
+    private final int charPositionInLine;
+    private final int startIndex;
+    private final int stopIndex;
+
+    Token44444(final Pair<TokenSource, CharStream> source, final int type, final int line, final int charPositionInLine,
+            final int startIndex, final int stopIndex) {
+        super(source);
+        this.type = type;
+        this.line = line;
+        this.charPositionInLine = charPositionInLine;
+        this.startIndex = startIndex;
+        this.stopIndex = stopIndex;
+    }
+
+    @Override
+    public int getType() {
+        return type;
+    }
+
+    @Override
+    public int getLine() {
+        return line;
+    }
+
+    @Override
+    public int getCharPositionInLine() {
+        return charPositionInLine;
+    }
+
+    @Override
+    public int getStartIndex() {
+        return startIndex;
+    }
+
+    @Override
+    public int getStopIndex() {
+        return stopIndex;
+    }
+}
index 6f35328e2191fe5a5a2c5ef9a1afd6d930045c1f..98d3aac98d6112264f990566e15c613671e36bfd 100644 (file)
@@ -35,6 +35,7 @@ import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser;
 import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.FileContext;
 import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.KeywordContext;
 import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
+import org.opendaylight.yangtools.yang.parser.rfc7950.antlr.CompactYangStatementLexer;
 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
@@ -163,7 +164,7 @@ public final class YangStatementStreamSource extends AbstractIdentifiable<Source
 
     private static StatementContext parseYangSource(final SourceIdentifier source, final InputStream stream)
             throws IOException, YangSyntaxErrorException {
-        final YangStatementLexer lexer = new YangStatementLexer(CharStreams.fromStream(stream));
+        final YangStatementLexer lexer = new CompactYangStatementLexer(CharStreams.fromStream(stream));
         final YangStatementParser parser = new YangStatementParser(new CommonTokenStream(lexer));
         // disconnect from console error output
         lexer.removeErrorListeners();