From 403e648e489aa93be0e3a1f213cfc984f516800e Mon Sep 17 00:00:00 2001 From: Tyler Gregg Date: Fri, 17 Jan 2025 11:20:28 -0800 Subject: [PATCH 1/2] Adds an Expression pool to reduce allocations. --- .../impl/IonReaderContinuableCoreBinary.java | 4 +- .../amazon/ion/impl/IonReaderTextSystemX.java | 2 +- .../ion/impl/macro/EExpressionArgsReader.java | 43 +- .../com/amazon/ion/impl/macro/Expression.kt | 64 +-- .../impl/macro/PooledExpressionFactory.java | 380 ++++++++++++++++++ 5 files changed, 437 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java diff --git a/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java b/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java index 69237ce5a..7a21c29bc 100644 --- a/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java +++ b/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java @@ -1647,7 +1647,7 @@ private void readGroupExpression(Macro.Parameter parameter, boolean requireSingl if (exitArgumentGroup() == Event.NEEDS_DATA) { throw new UnsupportedOperationException("TODO: support continuable parsing of macro arguments."); } - expressions.set(startIndex, new Expression.ExpressionGroup(startIndex, expressions.size())); + expressions.set(startIndex, expressionPool.createExpressionGroup(startIndex, expressions.size())); } /** @@ -1655,7 +1655,7 @@ private void readGroupExpression(Macro.Parameter parameter, boolean requireSingl */ private void addVoidExpression() { int startIndex = expressions.size(); - expressions.add(new Expression.ExpressionGroup(startIndex, startIndex + 1)); + expressions.add(expressionPool.createExpressionGroup(startIndex, startIndex + 1)); } @Override diff --git a/src/main/java/com/amazon/ion/impl/IonReaderTextSystemX.java b/src/main/java/com/amazon/ion/impl/IonReaderTextSystemX.java index f4144f5ce..37b722ef4 100644 --- a/src/main/java/com/amazon/ion/impl/IonReaderTextSystemX.java +++ b/src/main/java/com/amazon/ion/impl/IonReaderTextSystemX.java @@ -1255,7 +1255,7 @@ protected void readParameter(Macro.Parameter parameter, long parameterPresence, if (IonReaderTextSystemX.this.nextRaw() == null) { // Add an empty expression group if nothing present. int index = expressions.size() + 1; - expressions.add(new Expression.ExpressionGroup(index, index)); + expressions.add(expressionPool.createExpressionGroup(index, index)); return; } readValueAsExpression(isTrailing && parameter.getCardinality().canBeMulti); diff --git a/src/main/java/com/amazon/ion/impl/macro/EExpressionArgsReader.java b/src/main/java/com/amazon/ion/impl/macro/EExpressionArgsReader.java index 15bb6da65..e20eef554 100644 --- a/src/main/java/com/amazon/ion/impl/macro/EExpressionArgsReader.java +++ b/src/main/java/com/amazon/ion/impl/macro/EExpressionArgsReader.java @@ -7,7 +7,6 @@ import com.amazon.ion.impl.bin.PresenceBitmap; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -28,6 +27,8 @@ public abstract class EExpressionArgsReader { // Reusable sink for expressions. protected final List expressions = new ArrayList<>(16); + protected final PooledExpressionFactory expressionPool = new PooledExpressionFactory(); + /** * Constructor. * @param reader the {@link ReaderAdapter} from which to read {@link Expression}s. @@ -113,45 +114,45 @@ private void readScalarValueAsExpression( ) { Expression.EExpressionBodyExpression expression; if (reader.isNullValue()) { - expression = new Expression.NullValue(annotations, type); + expression = expressionPool.createNullValue(annotations, type); } else { switch (type) { case BOOL: - expression = new Expression.BoolValue(annotations, reader.booleanValue()); + expression = expressionPool.createBoolValue(annotations, reader.booleanValue()); break; case INT: switch (reader.getIntegerSize()) { case INT: case LONG: - expression = new Expression.LongIntValue(annotations, reader.longValue()); + expression = expressionPool.createLongIntValue(annotations, reader.longValue()); break; case BIG_INTEGER: - expression = new Expression.BigIntValue(annotations, reader.bigIntegerValue()); + expression = expressionPool.createBigIntValue(annotations, reader.bigIntegerValue()); break; default: throw new IllegalStateException(); } break; case FLOAT: - expression = new Expression.FloatValue(annotations, reader.doubleValue()); + expression = expressionPool.createFloatValue(annotations, reader.doubleValue()); break; case DECIMAL: - expression = new Expression.DecimalValue(annotations, reader.decimalValue()); + expression = expressionPool.createDecimalValue(annotations, reader.decimalValue()); break; case TIMESTAMP: - expression = new Expression.TimestampValue(annotations, reader.timestampValue()); + expression = expressionPool.createTimestampValue(annotations, reader.timestampValue()); break; case SYMBOL: - expression = new Expression.SymbolValue(annotations, reader.symbolValue()); + expression = expressionPool.createSymbolValue(annotations, reader.symbolValue()); break; case STRING: - expression = new Expression.StringValue(annotations, reader.stringValue()); + expression = expressionPool.createStringValue(annotations, reader.stringValue()); break; case CLOB: - expression = new Expression.ClobValue(annotations, reader.newBytes()); + expression = expressionPool.createClobValue(annotations, reader.newBytes()); break; case BLOB: - expression = new Expression.BlobValue(annotations, reader.newBytes()); + expression = expressionPool.createBlobValue(annotations, reader.newBytes()); break; default: throw new IllegalStateException(); @@ -177,7 +178,7 @@ private void readContainerValueAsExpression( stepInRaw(); while (nextRaw()) { if (type == IonType.STRUCT) { - expressions.add(new Expression.FieldName(reader.getFieldNameSymbol())); + expressions.add(expressionPool.createFieldName(reader.getFieldNameSymbol())); } readValueAsExpression(false); // TODO avoid recursion } @@ -186,18 +187,17 @@ private void readContainerValueAsExpression( // start and end indices of its expressions. Expression.EExpressionBodyExpression expression; if (isExpressionGroup) { - expression = new Expression.ExpressionGroup(startIndex, expressions.size()); + expression = expressionPool.createExpressionGroup(startIndex, expressions.size()); } else { switch (type) { case LIST: - expression = new Expression.ListValue(annotations, startIndex, expressions.size()); + expression = expressionPool.createListValue(annotations, startIndex, expressions.size()); break; case SEXP: - expression = new Expression.SExpValue(annotations, startIndex, expressions.size()); + expression = expressionPool.createSExpValue(annotations, startIndex, expressions.size()); break; case STRUCT: - // TODO consider whether templateStructIndex could be leveraged or should be removed - expression = new Expression.StructValue(annotations, startIndex, expressions.size(), Collections.emptyMap()); + expression = expressionPool.createStructValue(annotations, startIndex, expressions.size()); break; default: throw new IllegalStateException(); @@ -215,7 +215,7 @@ private void readStreamAsExpressionGroup() { do { readValueAsExpression(false); // TODO avoid recursion } while (nextRaw()); - expressions.set(startIndex, new Expression.ExpressionGroup(startIndex, expressions.size())); + expressions.set(startIndex, expressionPool.createExpressionGroup(startIndex, expressions.size())); } /** @@ -260,7 +260,7 @@ private void collectEExpressionArgs() { ); } stepOutOfEExpression(); - expressions.set(invocationStartIndex, new Expression.EExpression(macro, invocationStartIndex, expressions.size())); + expressions.set(invocationStartIndex, expressionPool.createEExpression(macro, invocationStartIndex, expressions.size())); } /** @@ -269,9 +269,10 @@ private void collectEExpressionArgs() { */ public void beginEvaluatingMacroInvocation(MacroEvaluator macroEvaluator) { expressions.clear(); + expressionPool.clear(); // TODO performance: avoid fully materializing all expressions up-front. if (reader.isInStruct()) { - expressions.add(new Expression.FieldName(reader.getFieldNameSymbol())); + expressions.add(expressionPool.createFieldName(reader.getFieldNameSymbol())); } collectEExpressionArgs(); macroEvaluator.initExpansion(expressions); diff --git a/src/main/java/com/amazon/ion/impl/macro/Expression.kt b/src/main/java/com/amazon/ion/impl/macro/Expression.kt index fc5f983a5..ff95cd260 100644 --- a/src/main/java/com/amazon/ion/impl/macro/Expression.kt +++ b/src/main/java/com/amazon/ion/impl/macro/Expression.kt @@ -28,7 +28,7 @@ sealed interface Expression { * The position of this expression in its containing list. * Child expressions (if any) start at `selfIndex + 1`. */ - val selfIndex: Int + var selfIndex: Int /** * The index of the first child expression (if any). * Always equal to `selfIndex + 1`. @@ -38,7 +38,7 @@ sealed interface Expression { * The exclusive end of the child expressions (if any). * If there are no child expressions, will be equal to [startInclusive]. */ - val endExclusive: Int + var endExclusive: Int } /** Marker interface representing expressions that can be present in E-Expressions. */ @@ -72,7 +72,7 @@ sealed interface Expression { * Interface for expressions that are _values_ in the Ion data model. */ sealed interface DataModelValue : DataModelExpression { - val annotations: List + var annotations: List val type: IonType fun withAnnotations(annotations: List): DataModelValue @@ -97,14 +97,14 @@ sealed interface Expression { * @property selfIndex the index of the first expression of the expression group (i.e. this instance) * @property endExclusive the index of the last expression contained in the expression group */ - data class ExpressionGroup(override val selfIndex: Int, override val endExclusive: Int) : EExpressionBodyExpression, TemplateBodyExpression, HasStartAndEnd + data class ExpressionGroup(override var selfIndex: Int, override var endExclusive: Int) : EExpressionBodyExpression, TemplateBodyExpression, HasStartAndEnd // Scalars - data class NullValue(override val annotations: List = emptyList(), override val type: IonType) : DataModelValue { + data class NullValue(override var annotations: List = emptyList(), override var type: IonType) : DataModelValue { override fun withAnnotations(annotations: List) = copy(annotations = annotations) } - data class BoolValue(override val annotations: List = emptyList(), val value: Boolean) : DataModelValue { + data class BoolValue(override var annotations: List = emptyList(), var value: Boolean) : DataModelValue { override val type: IonType get() = IonType.BOOL override fun withAnnotations(annotations: List) = copy(annotations = annotations) } @@ -114,31 +114,31 @@ sealed interface Expression { val longValue: Long } - data class LongIntValue(override val annotations: List = emptyList(), val value: Long) : IntValue { + data class LongIntValue(override var annotations: List = emptyList(), var value: Long) : IntValue { override val type: IonType get() = IonType.INT override fun withAnnotations(annotations: List) = copy(annotations = annotations) override val bigIntegerValue: BigInteger get() = BigInteger.valueOf(value) override val longValue: Long get() = value } - data class BigIntValue(override val annotations: List = emptyList(), val value: BigInteger) : IntValue { + data class BigIntValue(override var annotations: List = emptyList(), var value: BigInteger) : IntValue { override val type: IonType get() = IonType.INT override fun withAnnotations(annotations: List) = copy(annotations = annotations) override val bigIntegerValue: BigInteger get() = value override val longValue: Long get() = value.longValueExact() } - data class FloatValue(override val annotations: List = emptyList(), val value: Double) : DataModelValue { + data class FloatValue(override var annotations: List = emptyList(), var value: Double) : DataModelValue { override val type: IonType get() = IonType.FLOAT override fun withAnnotations(annotations: List) = copy(annotations = annotations) } - data class DecimalValue(override val annotations: List = emptyList(), val value: BigDecimal) : DataModelValue { + data class DecimalValue(override var annotations: List = emptyList(), var value: BigDecimal) : DataModelValue { override val type: IonType get() = IonType.DECIMAL override fun withAnnotations(annotations: List) = copy(annotations = annotations) } - data class TimestampValue(override val annotations: List = emptyList(), val value: Timestamp) : DataModelValue { + data class TimestampValue(override var annotations: List = emptyList(), var value: Timestamp) : DataModelValue { override val type: IonType get() = IonType.TIMESTAMP override fun withAnnotations(annotations: List) = copy(annotations = annotations) } @@ -147,13 +147,13 @@ sealed interface Expression { val stringValue: String } - data class StringValue(override val annotations: List = emptyList(), val value: String) : TextValue { + data class StringValue(override var annotations: List = emptyList(), var value: String) : TextValue { override val type: IonType get() = IonType.STRING override val stringValue: String get() = value override fun withAnnotations(annotations: List) = copy(annotations = annotations) } - data class SymbolValue(override val annotations: List = emptyList(), val value: SymbolToken) : TextValue { + data class SymbolValue(override var annotations: List = emptyList(), var value: SymbolToken) : TextValue { override val type: IonType get() = IonType.SYMBOL override val stringValue: String get() = value.assumeText() override fun withAnnotations(annotations: List) = copy(annotations = annotations) @@ -166,7 +166,7 @@ sealed interface Expression { } // We must override hashcode and equals in the lob types because `value` is a `byte[]` - data class BlobValue(override val annotations: List = emptyList(), override val value: ByteArray) : LobValue { + data class BlobValue(override var annotations: List = emptyList(), override var value: ByteArray) : LobValue { override val type: IonType get() = IonType.BLOB override fun withAnnotations(annotations: List) = copy(annotations = annotations) override fun hashCode(): Int = annotations.hashCode() * 31 + value.contentHashCode() @@ -178,7 +178,7 @@ sealed interface Expression { } } - data class ClobValue(override val annotations: List = emptyList(), override val value: ByteArray) : LobValue { + data class ClobValue(override var annotations: List = emptyList(), override var value: ByteArray) : LobValue { override val type: IonType get() = IonType.CLOB override fun withAnnotations(annotations: List) = copy(annotations = annotations) override fun hashCode(): Int = annotations.hashCode() * 31 + value.contentHashCode() @@ -197,9 +197,9 @@ sealed interface Expression { * @property endExclusive the index of the last expression contained in the list */ data class ListValue( - override val annotations: List = emptyList(), - override val selfIndex: Int, - override val endExclusive: Int + override var annotations: List = emptyList(), + override var selfIndex: Int, + override var endExclusive: Int ) : DataModelContainer { override val type: IonType get() = IonType.LIST override fun withAnnotations(annotations: List) = copy(annotations = annotations) @@ -209,9 +209,9 @@ sealed interface Expression { * An Ion SExp that could contain variables or macro invocations. */ data class SExpValue( - override val annotations: List = emptyList(), - override val selfIndex: Int, - override val endExclusive: Int + override var annotations: List = emptyList(), + override var selfIndex: Int, + override var endExclusive: Int ) : DataModelContainer { override val type: IonType get() = IonType.SEXP override fun withAnnotations(annotations: List) = copy(annotations = annotations) @@ -221,21 +221,21 @@ sealed interface Expression { * An Ion Struct that could contain variables or macro invocations. */ data class StructValue( - override val annotations: List = emptyList(), - override val selfIndex: Int, - override val endExclusive: Int, + override var annotations: List = emptyList(), + override var selfIndex: Int, + override var endExclusive: Int, val templateStructIndex: Map> ) : DataModelContainer { override val type: IonType get() = IonType.STRUCT override fun withAnnotations(annotations: List) = copy(annotations = annotations) } - data class FieldName(val value: SymbolToken) : DataModelExpression + data class FieldName(var value: SymbolToken) : DataModelExpression /** * A reference to a variable that needs to be expanded. */ - data class VariableRef(val signatureIndex: Int) : TemplateBodyExpression + data class VariableRef(var signatureIndex: Int) : TemplateBodyExpression sealed interface InvokableExpression : HasStartAndEnd, Expression { val macro: Macro @@ -245,17 +245,17 @@ sealed interface Expression { * A macro invocation that needs to be expanded. */ data class MacroInvocation( - override val macro: Macro, - override val selfIndex: Int, - override val endExclusive: Int + override var macro: Macro, + override var selfIndex: Int, + override var endExclusive: Int ) : TemplateBodyExpression, HasStartAndEnd, InvokableExpression /** * An e-expression that needs to be expanded. */ data class EExpression( - override val macro: Macro, - override val selfIndex: Int, - override val endExclusive: Int + override var macro: Macro, + override var selfIndex: Int, + override var endExclusive: Int ) : EExpressionBodyExpression, HasStartAndEnd, InvokableExpression } diff --git a/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java b/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java new file mode 100644 index 000000000..e48bd13e9 --- /dev/null +++ b/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java @@ -0,0 +1,380 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.ion.impl.macro; + +import com.amazon.ion.IonType; +import com.amazon.ion.SymbolToken; +import com.amazon.ion.Timestamp; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A factory for {@link Expression} instances. Avoids repetitive allocations by pooling instances. {@link #clear()} + * returns all instances to the pool. Note: this should only be used when the lifetime of the provided instances + * is known, and that lifetime must be the same for all instances provided between calls to {@link #clear()}. + */ +public class PooledExpressionFactory { + + private static final int POOL_SIZE = 32; + + private Expression.NullValue[] nullValues = new Expression.NullValue[POOL_SIZE]; + private Expression.BoolValue[] boolValues = new Expression.BoolValue[POOL_SIZE]; + private Expression.LongIntValue[] longIntValues = new Expression.LongIntValue[POOL_SIZE]; + private Expression.BigIntValue[] bigIntValues = new Expression.BigIntValue[POOL_SIZE]; + private Expression.FloatValue[] floatValues = new Expression.FloatValue[POOL_SIZE]; + private Expression.DecimalValue[] decimalValues = new Expression.DecimalValue[POOL_SIZE]; + private Expression.TimestampValue[] timestampValues = new Expression.TimestampValue[POOL_SIZE]; + private Expression.SymbolValue[] symbolValues = new Expression.SymbolValue[POOL_SIZE]; + private Expression.StringValue[] stringValues = new Expression.StringValue[POOL_SIZE]; + private Expression.ClobValue[] clobValues = new Expression.ClobValue[POOL_SIZE]; + private Expression.BlobValue[] blobValues = new Expression.BlobValue[POOL_SIZE]; + private Expression.FieldName[] fieldNames = new Expression.FieldName[POOL_SIZE]; + private Expression.EExpression[] eExpressions = new Expression.EExpression[POOL_SIZE]; + private Expression.ExpressionGroup[] expressionGroups = new Expression.ExpressionGroup[POOL_SIZE]; + private Expression.ListValue[] listValues = new Expression.ListValue[POOL_SIZE]; + private Expression.StructValue[] structValues = new Expression.StructValue[POOL_SIZE]; + private Expression.SExpValue[] sexpValues = new Expression.SExpValue[POOL_SIZE]; + + private int nullValuesIndex = 0; + private int boolValuesIndex = 0; + private int longIntValuesIndex = 0; + private int bigIntValuesIndex = 0; + private int floatValuesIndex = 0; + private int decimalValuesIndex = 0; + private int timestampValuesIndex = 0; + private int symbolValuesIndex = 0; + private int stringValuesIndex = 0; + private int clobValuesIndex = 0; + private int blobValuesIndex = 0; + private int fieldNamesIndex = 0; + private int eExpressionsIndex = 0; + private int expressionGroupsIndex = 0; + private int listValuesIndex = 0; + private int structValuesIndex = 0; + private int sexpValuesIndex = 0; + + private T[] grow(T[] array) { + return Arrays.copyOf(array, array.length * 2); + } + + public Expression.NullValue createNullValue(List annotations, IonType type) { + Expression.NullValue expression; + if (nullValuesIndex >= nullValues.length) { + nullValues = grow(nullValues); + } + expression = nullValues[nullValuesIndex]; + if (expression == null) { + expression = new Expression.NullValue(annotations, type); + nullValues[nullValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setType(type); + } + nullValuesIndex++; + return expression; + } + + public Expression.BoolValue createBoolValue(List annotations, boolean value) { + Expression.BoolValue expression; + if (boolValuesIndex >= boolValues.length) { + boolValues = grow(boolValues); + } + expression = boolValues[boolValuesIndex]; + if (expression == null) { + expression = new Expression.BoolValue(annotations, value); + boolValues[boolValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + boolValuesIndex++; + return expression; + } + + public Expression.LongIntValue createLongIntValue(List annotations, long value) { + Expression.LongIntValue expression; + if (longIntValuesIndex >= longIntValues.length) { + longIntValues = grow(longIntValues); + } + expression = longIntValues[longIntValuesIndex]; + if (expression == null) { + expression = new Expression.LongIntValue(annotations, value); + longIntValues[longIntValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + longIntValuesIndex++; + return expression; + } + + public Expression.BigIntValue createBigIntValue(List annotations, BigInteger value) { + Expression.BigIntValue expression; + if (bigIntValuesIndex >= bigIntValues.length) { + bigIntValues = grow(bigIntValues); + } + expression = bigIntValues[bigIntValuesIndex]; + if (expression == null) { + expression = new Expression.BigIntValue(annotations, value); + bigIntValues[bigIntValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + bigIntValuesIndex++; + return expression; + } + + + public Expression.FloatValue createFloatValue(List annotations, double value) { + Expression.FloatValue expression; + if (floatValuesIndex >= floatValues.length) { + floatValues = grow(floatValues); + } + expression = floatValues[floatValuesIndex]; + if (expression == null) { + expression = new Expression.FloatValue(annotations, value); + floatValues[floatValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + floatValuesIndex++; + return expression; + } + + public Expression.DecimalValue createDecimalValue(List annotations, BigDecimal value) { + Expression.DecimalValue expression; + if (decimalValuesIndex >= decimalValues.length) { + decimalValues = grow(decimalValues); + } + expression = decimalValues[decimalValuesIndex]; + if (expression == null) { + expression = new Expression.DecimalValue(annotations, value); + decimalValues[decimalValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + decimalValuesIndex++; + return expression; + } + + public Expression.TimestampValue createTimestampValue(List annotations, Timestamp value) { + Expression.TimestampValue expression; + if (timestampValuesIndex >= timestampValues.length) { + timestampValues = grow(timestampValues); + } + expression = timestampValues[timestampValuesIndex]; + if (expression == null) { + expression = new Expression.TimestampValue(annotations, value); + timestampValues[timestampValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + timestampValuesIndex++; + return expression; + } + + public Expression.SymbolValue createSymbolValue(List annotations, SymbolToken value) { + Expression.SymbolValue expression; + if (symbolValuesIndex >= symbolValues.length) { + symbolValues = grow(symbolValues); + } + expression = symbolValues[symbolValuesIndex]; + if (expression == null) { + expression = new Expression.SymbolValue(annotations, value); + symbolValues[symbolValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + symbolValuesIndex++; + return expression; + } + + public Expression.StringValue createStringValue(List annotations, String value) { + Expression.StringValue expression; + if (stringValuesIndex >= stringValues.length) { + stringValues = grow(stringValues); + } + expression = stringValues[stringValuesIndex]; + if (expression == null) { + expression = new Expression.StringValue(annotations, value); + stringValues[stringValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + stringValuesIndex++; + return expression; + } + + public Expression.ClobValue createClobValue(List annotations, byte[] value) { + Expression.ClobValue expression; + if (clobValuesIndex >= clobValues.length) { + clobValues = grow(clobValues); + } + expression = clobValues[clobValuesIndex]; + if (expression == null) { + expression = new Expression.ClobValue(annotations, value); + clobValues[clobValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + clobValuesIndex++; + return expression; + } + + public Expression.BlobValue createBlobValue(List annotations, byte[] value) { + Expression.BlobValue expression; + if (blobValuesIndex >= blobValues.length) { + blobValues = grow(blobValues); + } + expression = blobValues[blobValuesIndex]; + if (expression == null) { + expression = new Expression.BlobValue(annotations, value); + blobValues[blobValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setValue(value); + } + blobValuesIndex++; + return expression; + } + + public Expression.FieldName createFieldName(SymbolToken name) { + Expression.FieldName expression; + if (fieldNamesIndex >= fieldNames.length) { + fieldNames = grow(fieldNames); + } + expression = fieldNames[fieldNamesIndex]; + if (expression == null) { + expression = new Expression.FieldName(name); + fieldNames[fieldNamesIndex] = expression; + } else { + expression.setValue(name); + } + fieldNamesIndex++; + return expression; + } + + public Expression.EExpression createEExpression(Macro macro, int selfIndex, int endExclusive) { + Expression.EExpression expression; + if (eExpressionsIndex >= eExpressions.length) { + eExpressions = grow(eExpressions); + } + expression = eExpressions[eExpressionsIndex]; + if (expression == null) { + expression = new Expression.EExpression(macro, selfIndex, endExclusive); + eExpressions[eExpressionsIndex] = expression; + } else { + expression.setMacro(macro); + expression.setSelfIndex(selfIndex); + expression.setEndExclusive(endExclusive); + } + eExpressionsIndex++; + return expression; + } + + public Expression.ExpressionGroup createExpressionGroup(int selfIndex, int endExclusive) { + Expression.ExpressionGroup expression; + if (expressionGroupsIndex >= expressionGroups.length) { + expressionGroups = grow(expressionGroups); + } + expression = expressionGroups[expressionGroupsIndex]; + if (expression == null) { + expression = new Expression.ExpressionGroup(selfIndex, endExclusive); + expressionGroups[expressionGroupsIndex] = expression; + } else { + expression.setSelfIndex(selfIndex); + expression.setEndExclusive(endExclusive); + } + expressionGroupsIndex++; + return expression; + } + + public Expression.ListValue createListValue(List annotations, int selfIndex, int endExclusive) { + Expression.ListValue expression; + if (listValuesIndex >= listValues.length) { + listValues = grow(listValues); + } + expression = listValues[listValuesIndex]; + if (expression == null) { + expression = new Expression.ListValue(annotations, selfIndex, endExclusive); + listValues[listValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setSelfIndex(selfIndex); + expression.setEndExclusive(endExclusive); + } + listValuesIndex++; + return expression; + } + + public Expression.StructValue createStructValue(List annotations, int selfIndex, int endExclusive) { + Expression.StructValue expression; + if (structValuesIndex >= structValues.length) { + structValues = grow(structValues); + } + expression = structValues[structValuesIndex]; + if (expression == null) { + // TODO consider whether templateStructIndex could be leveraged or should be removed + expression = new Expression.StructValue(annotations, selfIndex, endExclusive, Collections.emptyMap()); + structValues[structValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setSelfIndex(selfIndex); + expression.setEndExclusive(endExclusive); + } + structValuesIndex++; + return expression; + } + + public Expression.SExpValue createSExpValue(List annotations, int selfIndex, int endExclusive) { + Expression.SExpValue expression; + if (sexpValuesIndex >= sexpValues.length) { + sexpValues = grow(sexpValues); + } + expression = sexpValues[sexpValuesIndex]; + if (expression == null) { + expression = new Expression.SExpValue(annotations, selfIndex, endExclusive); + sexpValues[sexpValuesIndex] = expression; + } else { + expression.setAnnotations(annotations); + expression.setSelfIndex(selfIndex); + expression.setEndExclusive(endExclusive); + } + sexpValuesIndex++; + return expression; + } + + /** + * Returns all instances to the pool. + */ + public void clear() { + nullValuesIndex = 0; + boolValuesIndex = 0; + longIntValuesIndex = 0; + bigIntValuesIndex = 0; + floatValuesIndex = 0; + decimalValuesIndex = 0; + timestampValuesIndex = 0; + symbolValuesIndex = 0; + stringValuesIndex = 0; + clobValuesIndex = 0; + blobValuesIndex = 0; + fieldNamesIndex = 0; + eExpressionsIndex = 0; + expressionGroupsIndex = 0; + listValuesIndex = 0; + structValuesIndex = 0; + sexpValuesIndex = 0; + } +} From 8c643739e27551f0b4244b449ca53f8333281905 Mon Sep 17 00:00:00 2001 From: Tyler Gregg Date: Mon, 27 Jan 2025 14:36:48 -0800 Subject: [PATCH 2/2] Do not copy when adding annotations to Expression instances; use an expression pool during evaluation in addition to parsing. --- .../com/amazon/ion/impl/macro/Expression.kt | 70 +++++++++++++++---- .../amazon/ion/impl/macro/MacroEvaluator.kt | 29 +++++--- .../impl/macro/PooledExpressionFactory.java | 36 +++++----- 3 files changed, 92 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/amazon/ion/impl/macro/Expression.kt b/src/main/java/com/amazon/ion/impl/macro/Expression.kt index ff95cd260..b49302297 100644 --- a/src/main/java/com/amazon/ion/impl/macro/Expression.kt +++ b/src/main/java/com/amazon/ion/impl/macro/Expression.kt @@ -101,12 +101,18 @@ sealed interface Expression { // Scalars data class NullValue(override var annotations: List = emptyList(), override var type: IonType) : DataModelValue { - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): NullValue { + this.annotations = annotations + return this + } } data class BoolValue(override var annotations: List = emptyList(), var value: Boolean) : DataModelValue { override val type: IonType get() = IonType.BOOL - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): BoolValue { + this.annotations = annotations + return this + } } sealed interface IntValue : DataModelValue { @@ -116,31 +122,46 @@ sealed interface Expression { data class LongIntValue(override var annotations: List = emptyList(), var value: Long) : IntValue { override val type: IonType get() = IonType.INT - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): LongIntValue { + this.annotations = annotations + return this + } override val bigIntegerValue: BigInteger get() = BigInteger.valueOf(value) override val longValue: Long get() = value } data class BigIntValue(override var annotations: List = emptyList(), var value: BigInteger) : IntValue { override val type: IonType get() = IonType.INT - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): BigIntValue { + this.annotations = annotations + return this + } override val bigIntegerValue: BigInteger get() = value override val longValue: Long get() = value.longValueExact() } data class FloatValue(override var annotations: List = emptyList(), var value: Double) : DataModelValue { override val type: IonType get() = IonType.FLOAT - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): FloatValue { + this.annotations = annotations + return this + } } data class DecimalValue(override var annotations: List = emptyList(), var value: BigDecimal) : DataModelValue { override val type: IonType get() = IonType.DECIMAL - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): DecimalValue { + this.annotations = annotations + return this + } } data class TimestampValue(override var annotations: List = emptyList(), var value: Timestamp) : DataModelValue { override val type: IonType get() = IonType.TIMESTAMP - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): TimestampValue { + this.annotations = annotations + return this + } } sealed interface TextValue : DataModelValue { @@ -150,13 +171,19 @@ sealed interface Expression { data class StringValue(override var annotations: List = emptyList(), var value: String) : TextValue { override val type: IonType get() = IonType.STRING override val stringValue: String get() = value - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): StringValue { + this.annotations = annotations + return this + } } data class SymbolValue(override var annotations: List = emptyList(), var value: SymbolToken) : TextValue { override val type: IonType get() = IonType.SYMBOL override val stringValue: String get() = value.assumeText() - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): SymbolValue { + this.annotations = annotations + return this + } } sealed interface LobValue : DataModelValue { @@ -168,7 +195,10 @@ sealed interface Expression { // We must override hashcode and equals in the lob types because `value` is a `byte[]` data class BlobValue(override var annotations: List = emptyList(), override var value: ByteArray) : LobValue { override val type: IonType get() = IonType.BLOB - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): BlobValue { + this.annotations = annotations + return this + } override fun hashCode(): Int = annotations.hashCode() * 31 + value.contentHashCode() override fun equals(other: Any?): Boolean { if (this === other) return true @@ -180,7 +210,10 @@ sealed interface Expression { data class ClobValue(override var annotations: List = emptyList(), override var value: ByteArray) : LobValue { override val type: IonType get() = IonType.CLOB - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): ClobValue { + this.annotations = annotations + return this + } override fun hashCode(): Int = annotations.hashCode() * 31 + value.contentHashCode() override fun equals(other: Any?): Boolean { if (this === other) return true @@ -202,7 +235,10 @@ sealed interface Expression { override var endExclusive: Int ) : DataModelContainer { override val type: IonType get() = IonType.LIST - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): ListValue { + this.annotations = annotations + return this + } } /** @@ -214,7 +250,10 @@ sealed interface Expression { override var endExclusive: Int ) : DataModelContainer { override val type: IonType get() = IonType.SEXP - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): SExpValue { + this.annotations = annotations + return this + } } /** @@ -227,7 +266,10 @@ sealed interface Expression { val templateStructIndex: Map> ) : DataModelContainer { override val type: IonType get() = IonType.STRUCT - override fun withAnnotations(annotations: List) = copy(annotations = annotations) + override fun withAnnotations(annotations: List): StructValue { + this.annotations = annotations + return this + } } data class FieldName(var value: SymbolToken) : DataModelExpression diff --git a/src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt b/src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt index 2f6249a1a..8dbdab125 100644 --- a/src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt +++ b/src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt @@ -10,6 +10,8 @@ import com.amazon.ion.util.unreachable import java.io.ByteArrayOutputStream import java.math.BigDecimal import java.math.BigInteger +import java.util.* +import kotlin.collections.ArrayList /** * Evaluates an EExpression from a List of [EExpressionBodyExpression] and the [TemplateBodyExpression]s @@ -52,6 +54,8 @@ class MacroEvaluator { private var numExpandedExpressions = 0 /** Pool of [ExpansionInfo] to minimize allocation and garbage collection. */ private val expanderPool: ArrayList = ArrayList(32) + /** Pool of [Expression] to minimize allocation and garbage collection. */ + val expressionPool: PooledExpressionFactory = PooledExpressionFactory() /** Gets an [ExpansionInfo] from the pool (or allocates a new one if necessary), initializing it with the provided values. */ fun getExpander(expansionKind: ExpansionKind, expressions: List, startInclusive: Int, endExclusive: Int, environment: Environment): ExpansionInfo { @@ -87,6 +91,7 @@ class MacroEvaluator { fun reset() { numExpandedExpressions = 0 + expressionPool.clear() } } @@ -279,7 +284,7 @@ class MacroEvaluator { } } thisExpansion.expansionKind = Empty - return StringValue(value = sb.toString()) + return thisExpansion.session.expressionPool.createStringValue(Collections.emptyList(), sb.toString()) } }, MakeSymbol { @@ -298,7 +303,7 @@ class MacroEvaluator { is FieldName -> unreachable() } } - return SymbolValue(value = newSymbolToken(sb.toString())) + return thisExpansion.session.expressionPool.createSymbolValue(Collections.emptyList(), newSymbolToken(sb.toString())) } }, MakeBlob { @@ -314,7 +319,7 @@ class MacroEvaluator { } } thisExpansion.expansionKind = Empty - return BlobValue(value = baos.toByteArray()) + return thisExpansion.session.expressionPool.createBlobValue(Collections.emptyList(), baos.toByteArray()) } }, MakeDecimal { @@ -325,7 +330,7 @@ class MacroEvaluator { val coefficient = thisExpansion.readExactlyOneArgument(COEFFICIENT_ARG).bigIntegerValue val exponent = thisExpansion.readExactlyOneArgument(EXPONENT_ARG).bigIntegerValue thisExpansion.expansionKind = Empty - return DecimalValue(value = BigDecimal(coefficient, -1 * exponent.intValueExact())) + return thisExpansion.session.expressionPool.createDecimalValue(Collections.emptyList(), BigDecimal(coefficient, -1 * exponent.intValueExact())) } }, MakeTimestamp { @@ -379,7 +384,7 @@ class MacroEvaluator { } } thisExpansion.expansionKind = Empty - return TimestampValue(value = ts) + return thisExpansion.session.expressionPool.createTimestampValue(Collections.emptyList(), ts) } catch (e: IllegalArgumentException) { throw IonException(e.message) } @@ -391,10 +396,12 @@ class MacroEvaluator { override fun produceNext(thisExpansion: ExpansionInfo): ExpansionOutputExpressionOrContinue { val fieldName = thisExpansion.readExactlyOneArgument(FIELD_NAME) - val fieldNameExpression = when (fieldName) { - is SymbolValue -> FieldName(fieldName.value) - is StringValue -> FieldName(newSymbolToken(fieldName.value)) - } + val fieldNameExpression = thisExpansion.session.expressionPool.createFieldName( + when (fieldName) { + is SymbolValue -> fieldName.value + is StringValue -> newSymbolToken(fieldName.value) + } + ) thisExpansion.readExactlyOneArgument(FIELD_VALUE) @@ -490,7 +497,7 @@ class MacroEvaluator { val a = thisExpansion.readExactlyOneArgument(ARG_A).bigIntegerValue val b = thisExpansion.readExactlyOneArgument(ARG_B).bigIntegerValue thisExpansion.expansionKind = Empty - return BigIntValue(value = a + b) + return thisExpansion.session.expressionPool.createBigIntValue(Collections.emptyList(), a + b) } }, Delta { @@ -510,7 +517,7 @@ class MacroEvaluator { val nextDelta = nextExpandedArg.bigIntegerValue val nextOutput = runningTotal + nextDelta thisExpansion.additionalState = nextOutput - return BigIntValue(value = nextOutput) + return thisExpansion.session.expressionPool.createBigIntValue(Collections.emptyList(), nextOutput) } EndOfExpansion -> return EndOfExpansion else -> throw IonException("delta arguments must be integers") diff --git a/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java b/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java index e48bd13e9..02093e040 100644 --- a/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java +++ b/src/main/java/com/amazon/ion/impl/macro/PooledExpressionFactory.java @@ -19,25 +19,25 @@ */ public class PooledExpressionFactory { - private static final int POOL_SIZE = 32; + private static final int INITIAL_POOL_SIZE = 32; - private Expression.NullValue[] nullValues = new Expression.NullValue[POOL_SIZE]; - private Expression.BoolValue[] boolValues = new Expression.BoolValue[POOL_SIZE]; - private Expression.LongIntValue[] longIntValues = new Expression.LongIntValue[POOL_SIZE]; - private Expression.BigIntValue[] bigIntValues = new Expression.BigIntValue[POOL_SIZE]; - private Expression.FloatValue[] floatValues = new Expression.FloatValue[POOL_SIZE]; - private Expression.DecimalValue[] decimalValues = new Expression.DecimalValue[POOL_SIZE]; - private Expression.TimestampValue[] timestampValues = new Expression.TimestampValue[POOL_SIZE]; - private Expression.SymbolValue[] symbolValues = new Expression.SymbolValue[POOL_SIZE]; - private Expression.StringValue[] stringValues = new Expression.StringValue[POOL_SIZE]; - private Expression.ClobValue[] clobValues = new Expression.ClobValue[POOL_SIZE]; - private Expression.BlobValue[] blobValues = new Expression.BlobValue[POOL_SIZE]; - private Expression.FieldName[] fieldNames = new Expression.FieldName[POOL_SIZE]; - private Expression.EExpression[] eExpressions = new Expression.EExpression[POOL_SIZE]; - private Expression.ExpressionGroup[] expressionGroups = new Expression.ExpressionGroup[POOL_SIZE]; - private Expression.ListValue[] listValues = new Expression.ListValue[POOL_SIZE]; - private Expression.StructValue[] structValues = new Expression.StructValue[POOL_SIZE]; - private Expression.SExpValue[] sexpValues = new Expression.SExpValue[POOL_SIZE]; + private Expression.NullValue[] nullValues = new Expression.NullValue[INITIAL_POOL_SIZE]; + private Expression.BoolValue[] boolValues = new Expression.BoolValue[INITIAL_POOL_SIZE]; + private Expression.LongIntValue[] longIntValues = new Expression.LongIntValue[INITIAL_POOL_SIZE]; + private Expression.BigIntValue[] bigIntValues = new Expression.BigIntValue[INITIAL_POOL_SIZE]; + private Expression.FloatValue[] floatValues = new Expression.FloatValue[INITIAL_POOL_SIZE]; + private Expression.DecimalValue[] decimalValues = new Expression.DecimalValue[INITIAL_POOL_SIZE]; + private Expression.TimestampValue[] timestampValues = new Expression.TimestampValue[INITIAL_POOL_SIZE]; + private Expression.SymbolValue[] symbolValues = new Expression.SymbolValue[INITIAL_POOL_SIZE]; + private Expression.StringValue[] stringValues = new Expression.StringValue[INITIAL_POOL_SIZE]; + private Expression.ClobValue[] clobValues = new Expression.ClobValue[INITIAL_POOL_SIZE]; + private Expression.BlobValue[] blobValues = new Expression.BlobValue[INITIAL_POOL_SIZE]; + private Expression.FieldName[] fieldNames = new Expression.FieldName[INITIAL_POOL_SIZE]; + private Expression.EExpression[] eExpressions = new Expression.EExpression[INITIAL_POOL_SIZE]; + private Expression.ExpressionGroup[] expressionGroups = new Expression.ExpressionGroup[INITIAL_POOL_SIZE]; + private Expression.ListValue[] listValues = new Expression.ListValue[INITIAL_POOL_SIZE]; + private Expression.StructValue[] structValues = new Expression.StructValue[INITIAL_POOL_SIZE]; + private Expression.SExpValue[] sexpValues = new Expression.SExpValue[INITIAL_POOL_SIZE]; private int nullValuesIndex = 0; private int boolValuesIndex = 0;