You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by mh...@apache.org on 2026/07/21 12:27:34 UTC
(asterixdb) branch master updated: [ASTERIXDB-3789][RT] Optimize FieldAccessByNameEvalFactory for constant field names
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git
The following commit(s) were added to refs/heads/master by this push:
new 2dbf57060d [ASTERIXDB-3789][RT] Optimize FieldAccessByNameEvalFactory for constant field names
2dbf57060d is described below
commit 2dbf57060dc84bef1c00e0132c1617ffee72992a
Author: preetham0202 <pr...@couchbase.com>
AuthorDate: Fri Jun 26 12:21:54 2026 +0530
[ASTERIXDB-3789][RT] Optimize FieldAccessByNameEvalFactory for constant field names
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
When fldNameEvalFactory is a ConstantEvalFactory (the common case for
literal field access, e.g. r.foo), the UTF-8 length, meta-length, and
hash of the field name were recomputed on every tuple evaluation.
Ext-ref: MB-72643
Change-Id: I4d6cd6e14c1e1b917bb32a921f8776abfd9642e0
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21369
Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
Reviewed-by: Ali Alsuliman <al...@gmail.com>
Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
---
.../serde/ARecordSerializerDeserializer.java | 23 ++++--
.../records/FieldAccessByNameEvalFactory.java | 87 ++++++++++++++++++++++
2 files changed, 103 insertions(+), 7 deletions(-)
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/dataflow/data/nontagged/serde/ARecordSerializerDeserializer.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/dataflow/data/nontagged/serde/ARecordSerializerDeserializer.java
index e955756320..ca5089e2f6 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/dataflow/data/nontagged/serde/ARecordSerializerDeserializer.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/dataflow/data/nontagged/serde/ARecordSerializerDeserializer.java
@@ -44,6 +44,7 @@ import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
import org.apache.hyracks.api.exceptions.HyracksDataException;
import org.apache.hyracks.data.std.primitive.IntegerPointable;
import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.util.annotations.AiProvenance;
import org.apache.hyracks.util.string.UTF8StringUtil;
public class ARecordSerializerDeserializer implements ISerializerDeserializer<ARecord> {
@@ -299,8 +300,20 @@ public class ARecordSerializerDeserializer implements ISerializerDeserializer<AR
return offset + AInt32SerializerDeserializer.getInt(serRecord, pointer + nullBitmapSize + (4 * fieldId));
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool = AiProvenance.Tool.CLAUDE_CODE_CLI, contributionKind = AiProvenance.ContributionKind.REFACTORED, notes = "Delegates to pre-computed-hash overload to enable constant field-name optimisation")
public static int getFieldOffsetByName(byte[] serRecord, int start, int len, byte[] fieldName, int nstart,
IBinaryHashFunction nameHashFunction, IBinaryComparator nameComparator) throws HyracksDataException {
+ int fieldUtflength = UTF8StringUtil.getUTFLength(fieldName, nstart + 1);
+ int fieldUtfMetaLen = UTF8StringUtil.getNumBytesToStoreLength(fieldUtflength);
+ int fieldNameHashCode = nameHashFunction.hash(fieldName, nstart + 1, fieldUtflength + fieldUtfMetaLen);
+ return getFieldOffsetByName(serRecord, start, len, fieldName, nstart, fieldUtflength, fieldUtfMetaLen,
+ fieldNameHashCode, nameComparator);
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool = AiProvenance.Tool.CLAUDE_CODE_CLI, contributionKind = AiProvenance.ContributionKind.GENERATED, notes = "Accepts pre-computed UTF-8 lengths and hash code; called directly from constant field-name path")
+ public static int getFieldOffsetByName(byte[] serRecord, int start, int len, byte[] fieldName, int nstart,
+ int fieldUtflength, int fieldUtfMetaLen, int fieldNameHash, IBinaryComparator nameComparator)
+ throws HyracksDataException {
// 5 is the index of the byte that determines whether the record is expanded or not, i.e. it has an open part.
if (hasNoFields(serRecord, start, len) || serRecord[start + 5] != 1) {
return -1;
@@ -308,9 +321,6 @@ public class ARecordSerializerDeserializer implements ISerializerDeserializer<AR
// 6 is the index of the first byte of the openPartOffset value.
int openPartOffset = start + AInt32SerializerDeserializer.getInt(serRecord, start + 6);
int numberOfOpenField = AInt32SerializerDeserializer.getInt(serRecord, openPartOffset);
- int fieldUtflength = UTF8StringUtil.getUTFLength(fieldName, nstart + 1);
- int fieldUtfMetaLen = UTF8StringUtil.getNumBytesToStoreLength(fieldUtflength);
- int fieldNameHashCode = nameHashFunction.hash(fieldName, nstart + 1, fieldUtflength + fieldUtfMetaLen);
int offset = openPartOffset + 4;
int fieldOffset = -1;
@@ -321,7 +331,7 @@ public class ARecordSerializerDeserializer implements ISerializerDeserializer<AR
mid = (high + low) / 2;
// 8 = hash code (4) + offset to the (name + tag + value ) of the field (4).
int h = AInt32SerializerDeserializer.getInt(serRecord, offset + (8 * mid));
- if (h == fieldNameHashCode) {
+ if (h == fieldNameHash) {
fieldOffset = start + AInt32SerializerDeserializer.getInt(serRecord, offset + (8 * mid) + 4);
// the utf8 comparator do not require to put the precise length, we can just pass a estimated limit.
if (nameComparator.compare(serRecord, fieldOffset, len, fieldName, nstart + 1,
@@ -331,7 +341,7 @@ public class ARecordSerializerDeserializer implements ISerializerDeserializer<AR
} else { // this else part has not been tested yet
for (int j = mid + 1; j < numberOfOpenField; j++) {
h = AInt32SerializerDeserializer.getInt(serRecord, offset + (8 * j));
- if (h == fieldNameHashCode) {
+ if (h == fieldNameHash) {
fieldOffset = start + AInt32SerializerDeserializer.getInt(serRecord, offset + (8 * j) + 4);
if (nameComparator.compare(serRecord, fieldOffset, len, fieldName, nstart + 1,
fieldUtflength) == 0) {
@@ -343,12 +353,11 @@ public class ARecordSerializerDeserializer implements ISerializerDeserializer<AR
}
}
}
- if (fieldNameHashCode > h) {
+ if (fieldNameHash > h) {
low = mid + 1;
} else {
high = mid - 1;
}
-
}
return -1; // no field with this name.
}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/records/FieldAccessByNameEvalFactory.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/records/FieldAccessByNameEvalFactory.java
index ce2f2d6377..45d82afe3f 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/records/FieldAccessByNameEvalFactory.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/records/FieldAccessByNameEvalFactory.java
@@ -32,6 +32,7 @@ import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.algebricks.runtime.evaluators.ConstantEvalFactory;
import org.apache.hyracks.api.context.IEvaluatorContext;
import org.apache.hyracks.api.dataflow.value.IBinaryComparator;
import org.apache.hyracks.api.dataflow.value.IBinaryHashFunction;
@@ -41,6 +42,8 @@ import org.apache.hyracks.data.std.api.IPointable;
import org.apache.hyracks.data.std.primitive.VoidPointable;
import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+import org.apache.hyracks.util.annotations.AiProvenance;
+import org.apache.hyracks.util.string.UTF8StringUtil;
public class FieldAccessByNameEvalFactory implements IScalarEvaluatorFactory {
@@ -59,8 +62,23 @@ public class FieldAccessByNameEvalFactory implements IScalarEvaluatorFactory {
this.funID = funID;
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool = AiProvenance.Tool.CLAUDE_CODE_CLI, contributionKind = AiProvenance.ContributionKind.REFACTORED, notes = "Fast path for constant field name: pre-compute UTF-8 lengths and hash at evaluator init time")
@Override
public IScalarEvaluator createScalarEvaluator(final IEvaluatorContext ctx) throws HyracksDataException {
+ if (fldNameEvalFactory instanceof ConstantEvalFactory) {
+ byte[] constFldName = ((ConstantEvalFactory) fldNameEvalFactory).getValue();
+ if (constFldName[0] == ATypeTag.SERIALIZED_STRING_TYPE_TAG) {
+ // Pre-compute UTF-8 metadata and hash once; reuse them on every tuple evaluation.
+ IBinaryComparator comparator =
+ BinaryComparatorFactoryProvider.UTF8STRING_POINTABLE_INSTANCE.createBinaryComparator();
+ int constFldUtflength = UTF8StringUtil.getUTFLength(constFldName, 1);
+ int constFldUtfMetaLen = UTF8StringUtil.getNumBytesToStoreLength(constFldUtflength);
+ int constFldHash = BinaryHashFunctionFactoryProvider.UTF8STRING_POINTABLE_INSTANCE
+ .createBinaryHashFunction().hash(constFldName, 1, constFldUtflength + constFldUtfMetaLen);
+ return new ConstantFieldNameEvaluator(ctx, recordEvalFactory, constFldName, constFldUtflength,
+ constFldUtfMetaLen, constFldHash, comparator, sourceLoc, funID);
+ }
+ }
return new IScalarEvaluator() {
private final IBinaryHashFunction fieldNameHashFunction =
@@ -142,4 +160,73 @@ public class FieldAccessByNameEvalFactory implements IScalarEvaluatorFactory {
public FunctionIdentifier getFunID() {
return funID;
}
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool = AiProvenance.Tool.CLAUDE_CODE_CLI, contributionKind = AiProvenance.ContributionKind.GENERATED, notes = "Evaluator for the constant field-name fast path; pre-computed hash/lengths passed via constructor")
+ private static final class ConstantFieldNameEvaluator implements IScalarEvaluator {
+
+ private final IEvaluatorContext ctx;
+ private final IScalarEvaluator eval0;
+ private final byte[] constFldName;
+ private final int constFldUtflength;
+ private final int constFldUtfMetaLen;
+ private final int constFldHash;
+ private final IBinaryComparator fieldNameComparator;
+ private final SourceLocation sourceLoc;
+ private final FunctionIdentifier funID;
+ private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
+ private final DataOutput out = resultStorage.getDataOutput();
+ private final IPointable inputArg0 = new VoidPointable();
+
+ private ConstantFieldNameEvaluator(IEvaluatorContext ctx, IScalarEvaluatorFactory recordEvalFactory,
+ byte[] constFldName, int constFldUtflength, int constFldUtfMetaLen, int constFldHash,
+ IBinaryComparator fieldNameComparator, SourceLocation sourceLoc, FunctionIdentifier funID)
+ throws HyracksDataException {
+ this.ctx = ctx;
+ this.eval0 = recordEvalFactory.createScalarEvaluator(ctx);
+ this.constFldName = constFldName;
+ this.constFldUtflength = constFldUtflength;
+ this.constFldUtfMetaLen = constFldUtfMetaLen;
+ this.constFldHash = constFldHash;
+ this.fieldNameComparator = fieldNameComparator;
+ this.sourceLoc = sourceLoc;
+ this.funID = funID;
+ }
+
+ @Override
+ public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+ try {
+ resultStorage.reset();
+ eval0.evaluate(tuple, inputArg0);
+ if (PointableHelper.checkAndSetMissingOrNull(result, inputArg0)) {
+ return;
+ }
+ byte[] serRecord = inputArg0.getByteArray();
+ int serRecordOffset = inputArg0.getStartOffset();
+ int serRecordLen = inputArg0.getLength();
+ if (serRecord[serRecordOffset] != ATypeTag.SERIALIZED_RECORD_TYPE_TAG) {
+ ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, funID, serRecord[serRecordOffset], 0,
+ ATypeTag.OBJECT);
+ out.writeByte(ATypeTag.SERIALIZED_MISSING_TYPE_TAG);
+ result.set(resultStorage);
+ return;
+ }
+ int fieldValueOffset = ARecordSerializerDeserializer.getFieldOffsetByName(serRecord, serRecordOffset,
+ serRecordLen, constFldName, 0, constFldUtflength, constFldUtfMetaLen, constFldHash,
+ fieldNameComparator);
+ if (fieldValueOffset < 0) {
+ out.writeByte(ATypeTag.SERIALIZED_MISSING_TYPE_TAG);
+ result.set(resultStorage);
+ return;
+ }
+ ATypeTag fieldValueTypeTag =
+ EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(serRecord[fieldValueOffset]);
+ int fieldValueLength =
+ NonTaggedFormatUtil.getFieldValueLength(serRecord, fieldValueOffset, fieldValueTypeTag, true)
+ + 1;
+ result.set(serRecord, fieldValueOffset, fieldValueLength);
+ } catch (IOException e) {
+ throw HyracksDataException.create(e);
+ }
+ }
+ }
}