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);
+            }
+        }
+    }
 }