This is an automated email from the ASF dual-hosted git repository.

clintropolis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 4bf97a82b5f feat: simd min/max vector aggregators (#19660)
4bf97a82b5f is described below

commit 4bf97a82b5fdc70e1993e7060651b1c9279cfb8b
Author: Clint Wylie <[email protected]>
AuthorDate: Tue Jul 14 18:28:25 2026 -0700

    feat: simd min/max vector aggregators (#19660)
---
 .../benchmark/query/SqlExpressionBenchmark.java    |  16 +-
 .../aggregation/DoubleMaxAggregatorFactory.java    |   5 +
 .../aggregation/DoubleMinAggregatorFactory.java    |   5 +
 .../aggregation/FloatMaxAggregatorFactory.java     |   5 +
 .../aggregation/FloatMinAggregatorFactory.java     |   5 +
 .../aggregation/LongMaxAggregatorFactory.java      |   5 +
 .../aggregation/LongMinAggregatorFactory.java      |   5 +
 .../simd/SimdDoubleMaxVectorAggregator.java        |  97 +++++++
 .../simd/SimdDoubleMinVectorAggregator.java        |  97 +++++++
 .../simd/SimdFloatMaxVectorAggregator.java         |  97 +++++++
 .../simd/SimdFloatMinVectorAggregator.java         |  97 +++++++
 .../simd/SimdLongMaxVectorAggregator.java          |  97 +++++++
 .../simd/SimdLongMinVectorAggregator.java          |  97 +++++++
 .../simd/SimdAggregatorTestHelpers.java            | 191 ++++++++++++
 .../simd/SimdMinMaxVectorAggregatorTest.java       | 319 +++++++++++++++++++++
 .../simd/SimdSumVectorAggregatorTest.java          | 148 +---------
 ...lVectorizedExpressionResultConsistencyTest.java |   4 +-
 17 files changed, 1145 insertions(+), 145 deletions(-)

diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
index 1c2b3c723f5..28a3ac8672a 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
@@ -160,10 +160,17 @@ public class SqlExpressionBenchmark extends 
SqlBaseQueryBenchmark
       "SELECT NVL(long1, long3), SUM(double1) FROM expressions GROUP BY 1 
ORDER BY 2",
       "SELECT NVL(long1, long5 + long3), SUM(double1) FROM expressions GROUP 
BY 1 ORDER BY 2",
       "SELECT CASE WHEN MOD(long1, 2) = 0 THEN -1 WHEN MOD(long1, 2) = 1 THEN 
long2 / MOD(long1, 2) ELSE long3 END FROM expressions GROUP BY 1",
-      // cast
+      // 59-61 cast
       "SELECT CAST(string1 as BIGINT) + CAST(string3 as DOUBLE) + long3, 
COUNT(*) FROM expressions GROUP BY 1 ORDER BY 2",
       "SELECT COUNT(*), SUM(CAST(string1 as BIGINT) + CAST(string3 as BIGINT)) 
FROM expressions WHERE double3 < 1010.0 AND double3 > 100.0",
-      "SELECT COUNT(*) FROM expressions WHERE __time >= TIMESTAMP '2000-01-01 
00:00:00' AND __time < TIMESTAMP '2000-01-02 00:00:00' AND 
(UPPER(COALESCE(string3,'')) LIKE '1%' OR TRIM(UPPER(COALESCE(string3,''))) 
LIKE '1%' OR SUBSTRING(UPPER(COALESCE(string3,'')),1,1) IN 
('1','2','3','4','5') OR ('X' || UPPER(COALESCE(string3,''))) LIKE 'X1%') AND 
(UPPER(COALESCE(string5,'')) LIKE '2%' OR TRIM(UPPER(COALESCE(string5,''))) 
LIKE '2%' OR SUBSTRING(UPPER(COALESCE(string5,'')),1,1) IN ('1','2 [...]
+      "SELECT COUNT(*) FROM expressions WHERE __time >= TIMESTAMP '2000-01-01 
00:00:00' AND __time < TIMESTAMP '2000-01-02 00:00:00' AND 
(UPPER(COALESCE(string3,'')) LIKE '1%' OR TRIM(UPPER(COALESCE(string3,''))) 
LIKE '1%' OR SUBSTRING(UPPER(COALESCE(string3,'')),1,1) IN 
('1','2','3','4','5') OR ('X' || UPPER(COALESCE(string3,''))) LIKE 'X1%') AND 
(UPPER(COALESCE(string5,'')) LIKE '2%' OR TRIM(UPPER(COALESCE(string5,''))) 
LIKE '2%' OR SUBSTRING(UPPER(COALESCE(string5,'')),1,1) IN ('1','2 [...]
+      // ===========================
+      // more non-expression reference queries: min/max aggregators
+      // ===========================
+      // 62: min/max on a nullable long column (exercises the null-aware SIMD 
path)
+      "SELECT MIN(long5), MAX(long5) FROM expressions",
+      // 63: min/max across long/double/float, non-null columns
+      "SELECT MIN(long1), MAX(long1), MIN(double1), MAX(double1), MIN(float3), 
MAX(float3) FROM expressions"
   );
 
   @Param({
@@ -240,7 +247,10 @@ public class SqlExpressionBenchmark extends 
SqlBaseQueryBenchmark
       "57",
       "58",
       "59",
-      "60"
+      "60",
+      "61",
+      "62",
+      "63"
   })
   private String query;
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMaxAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMaxAggregatorFactory.java
index a4b2ea87e63..6550b024690 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMaxAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMaxAggregatorFactory.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Supplier;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.math.expr.ExpressionProcessing;
+import org.apache.druid.query.aggregation.simd.SimdDoubleMaxVectorAggregator;
 import org.apache.druid.segment.BaseDoubleColumnValueSelector;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
 import org.apache.druid.segment.vector.VectorValueSelector;
@@ -81,6 +83,9 @@ public class DoubleMaxAggregatorFactory extends 
SimpleDoubleAggregatorFactory
       VectorValueSelector selector
   )
   {
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdDoubleMaxVectorAggregator(selector);
+    }
     return new DoubleMaxVectorAggregator(selector);
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMinAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMinAggregatorFactory.java
index 241d7911af6..cb95d52ab69 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMinAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/DoubleMinAggregatorFactory.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Supplier;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.math.expr.ExpressionProcessing;
+import org.apache.druid.query.aggregation.simd.SimdDoubleMinVectorAggregator;
 import org.apache.druid.segment.BaseDoubleColumnValueSelector;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
 import org.apache.druid.segment.vector.VectorValueSelector;
@@ -81,6 +83,9 @@ public class DoubleMinAggregatorFactory extends 
SimpleDoubleAggregatorFactory
       VectorValueSelector selector
   )
   {
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdDoubleMinVectorAggregator(selector);
+    }
     return new DoubleMinVectorAggregator(selector);
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/FloatMaxAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/FloatMaxAggregatorFactory.java
index 4bfeba615cd..553f8103800 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/FloatMaxAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/FloatMaxAggregatorFactory.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Supplier;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.math.expr.ExpressionProcessing;
+import org.apache.druid.query.aggregation.simd.SimdFloatMaxVectorAggregator;
 import org.apache.druid.segment.BaseFloatColumnValueSelector;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
 import org.apache.druid.segment.vector.VectorValueSelector;
@@ -81,6 +83,9 @@ public class FloatMaxAggregatorFactory extends 
SimpleFloatAggregatorFactory
       VectorValueSelector selector
   )
   {
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdFloatMaxVectorAggregator(selector);
+    }
     return new FloatMaxVectorAggregator(selector);
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/FloatMinAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/FloatMinAggregatorFactory.java
index d720658a6b3..3355414f6c8 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/FloatMinAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/FloatMinAggregatorFactory.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Supplier;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.math.expr.ExpressionProcessing;
+import org.apache.druid.query.aggregation.simd.SimdFloatMinVectorAggregator;
 import org.apache.druid.segment.BaseFloatColumnValueSelector;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
 import org.apache.druid.segment.vector.VectorValueSelector;
@@ -81,6 +83,9 @@ public class FloatMinAggregatorFactory extends 
SimpleFloatAggregatorFactory
       VectorValueSelector selector
   )
   {
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdFloatMinVectorAggregator(selector);
+    }
     return new FloatMinVectorAggregator(selector);
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/LongMaxAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/LongMaxAggregatorFactory.java
index 1304d272ede..1caa9b6b622 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/LongMaxAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/LongMaxAggregatorFactory.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Supplier;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.math.expr.ExpressionProcessing;
+import org.apache.druid.query.aggregation.simd.SimdLongMaxVectorAggregator;
 import org.apache.druid.segment.BaseLongColumnValueSelector;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
 import org.apache.druid.segment.vector.VectorValueSelector;
@@ -81,6 +83,9 @@ public class LongMaxAggregatorFactory extends 
SimpleLongAggregatorFactory
       VectorValueSelector selector
   )
   {
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdLongMaxVectorAggregator(selector);
+    }
     return new LongMaxVectorAggregator(selector);
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/LongMinAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/LongMinAggregatorFactory.java
index 3073217986c..d6ff9547f44 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/LongMinAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/LongMinAggregatorFactory.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Supplier;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.math.expr.ExpressionProcessing;
+import org.apache.druid.query.aggregation.simd.SimdLongMinVectorAggregator;
 import org.apache.druid.segment.BaseLongColumnValueSelector;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
 import org.apache.druid.segment.vector.VectorValueSelector;
@@ -81,6 +83,9 @@ public class LongMinAggregatorFactory extends 
SimpleLongAggregatorFactory
           VectorValueSelector selector
   )
   {
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdLongMinVectorAggregator(selector);
+    }
     return new LongMinVectorAggregator(selector);
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMaxVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMaxVectorAggregator.java
new file mode 100644
index 00000000000..8a691f8ddd8
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMaxVectorAggregator.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.query.aggregation.DoubleMaxVectorAggregator;
+import org.apache.druid.query.aggregation.NullAwareVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link DoubleMaxVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * issues a hardcoded {@link DoubleVector#max} and a {@code 
reduceLanes(VectorOperators.MAX)} so the JIT emits the
+ * platform's double-max and double-max-reduce intrinsics. Null lanes preserve 
the lane's seeded
+ * {@link Double#NEGATIVE_INFINITY} via masked {@code lanewise} so the 
reduction is unaffected by them.
+ */
+public final class SimdDoubleMaxVectorAggregator extends 
DoubleMaxVectorAggregator implements NullAwareVectorAggregator
+{
+  private static final VectorSpecies<Double> SPECIES = 
DoubleVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdDoubleMaxVectorAggregator(VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(ByteBuffer buf, int position, int startRow, int endRow)
+  {
+    final double[] vector = selector.getDoubleVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    DoubleVector vacc = DoubleVector.broadcast(SPECIES, 
Double.NEGATIVE_INFINITY);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.max(DoubleVector.fromArray(SPECIES, vector, i));
+    }
+    double localMax = vacc.reduceLanes(VectorOperators.MAX);
+    for (; i < endRow; i++) {
+      localMax = Math.max(localMax, vector[i]);
+    }
+    buf.putDouble(position, Math.max(buf.getDouble(position), localMax));
+  }
+
+  @Override
+  public boolean aggregate(ByteBuffer buf, int position, int startRow, int 
endRow, boolean[] nullVector)
+  {
+    final double[] vector = selector.getDoubleVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    DoubleVector vacc = DoubleVector.broadcast(SPECIES, 
Double.NEGATIVE_INFINITY);
+    int nonNullCount = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Double> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.lanewise(VectorOperators.MAX, 
DoubleVector.fromArray(SPECIES, vector, i), notNull);
+      nonNullCount += notNull.trueCount();
+    }
+    double localMax = vacc.reduceLanes(VectorOperators.MAX);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        localMax = Math.max(localMax, vector[i]);
+        nonNullCount++;
+      }
+    }
+    if (nonNullCount > 0) {
+      buf.putDouble(position, Math.max(buf.getDouble(position), localMax));
+      return true;
+    }
+    return false;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMinVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMinVectorAggregator.java
new file mode 100644
index 00000000000..baa18c0924b
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMinVectorAggregator.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.query.aggregation.DoubleMinVectorAggregator;
+import org.apache.druid.query.aggregation.NullAwareVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link DoubleMinVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * issues a hardcoded {@link DoubleVector#min} and a {@code 
reduceLanes(VectorOperators.MIN)} so the JIT emits the
+ * platform's double-min and double-min-reduce intrinsics. Null lanes preserve 
the lane's seeded
+ * {@link Double#POSITIVE_INFINITY} via masked {@code lanewise} so the 
reduction is unaffected by them.
+ */
+public final class SimdDoubleMinVectorAggregator extends 
DoubleMinVectorAggregator implements NullAwareVectorAggregator
+{
+  private static final VectorSpecies<Double> SPECIES = 
DoubleVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdDoubleMinVectorAggregator(VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(ByteBuffer buf, int position, int startRow, int endRow)
+  {
+    final double[] vector = selector.getDoubleVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    DoubleVector vacc = DoubleVector.broadcast(SPECIES, 
Double.POSITIVE_INFINITY);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.min(DoubleVector.fromArray(SPECIES, vector, i));
+    }
+    double localMin = vacc.reduceLanes(VectorOperators.MIN);
+    for (; i < endRow; i++) {
+      localMin = Math.min(localMin, vector[i]);
+    }
+    buf.putDouble(position, Math.min(buf.getDouble(position), localMin));
+  }
+
+  @Override
+  public boolean aggregate(ByteBuffer buf, int position, int startRow, int 
endRow, boolean[] nullVector)
+  {
+    final double[] vector = selector.getDoubleVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    DoubleVector vacc = DoubleVector.broadcast(SPECIES, 
Double.POSITIVE_INFINITY);
+    int nonNullCount = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Double> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.lanewise(VectorOperators.MIN, 
DoubleVector.fromArray(SPECIES, vector, i), notNull);
+      nonNullCount += notNull.trueCount();
+    }
+    double localMin = vacc.reduceLanes(VectorOperators.MIN);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        localMin = Math.min(localMin, vector[i]);
+        nonNullCount++;
+      }
+    }
+    if (nonNullCount > 0) {
+      buf.putDouble(position, Math.min(buf.getDouble(position), localMin));
+      return true;
+    }
+    return false;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdFloatMaxVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdFloatMaxVectorAggregator.java
new file mode 100644
index 00000000000..05a5481d958
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdFloatMaxVectorAggregator.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.query.aggregation.FloatMaxVectorAggregator;
+import org.apache.druid.query.aggregation.NullAwareVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link FloatMaxVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * issues a hardcoded {@link FloatVector#max} and a {@code 
reduceLanes(VectorOperators.MAX)} so the JIT emits the
+ * platform's float-max and float-max-reduce intrinsics. Null lanes preserve 
the lane's seeded
+ * {@link Float#NEGATIVE_INFINITY} via masked {@code lanewise} so the 
reduction is unaffected by them.
+ */
+public final class SimdFloatMaxVectorAggregator extends 
FloatMaxVectorAggregator implements NullAwareVectorAggregator
+{
+  private static final VectorSpecies<Float> SPECIES = 
FloatVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdFloatMaxVectorAggregator(VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(ByteBuffer buf, int position, int startRow, int endRow)
+  {
+    final float[] vector = selector.getFloatVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    FloatVector vacc = FloatVector.broadcast(SPECIES, Float.NEGATIVE_INFINITY);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.max(FloatVector.fromArray(SPECIES, vector, i));
+    }
+    float localMax = vacc.reduceLanes(VectorOperators.MAX);
+    for (; i < endRow; i++) {
+      localMax = Math.max(localMax, vector[i]);
+    }
+    buf.putFloat(position, Math.max(buf.getFloat(position), localMax));
+  }
+
+  @Override
+  public boolean aggregate(ByteBuffer buf, int position, int startRow, int 
endRow, boolean[] nullVector)
+  {
+    final float[] vector = selector.getFloatVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    FloatVector vacc = FloatVector.broadcast(SPECIES, Float.NEGATIVE_INFINITY);
+    int nonNullCount = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Float> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.lanewise(VectorOperators.MAX, FloatVector.fromArray(SPECIES, 
vector, i), notNull);
+      nonNullCount += notNull.trueCount();
+    }
+    float localMax = vacc.reduceLanes(VectorOperators.MAX);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        localMax = Math.max(localMax, vector[i]);
+        nonNullCount++;
+      }
+    }
+    if (nonNullCount > 0) {
+      buf.putFloat(position, Math.max(buf.getFloat(position), localMax));
+      return true;
+    }
+    return false;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdFloatMinVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdFloatMinVectorAggregator.java
new file mode 100644
index 00000000000..05b61334c80
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdFloatMinVectorAggregator.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.query.aggregation.FloatMinVectorAggregator;
+import org.apache.druid.query.aggregation.NullAwareVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link FloatMinVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * issues a hardcoded {@link FloatVector#min} and a {@code 
reduceLanes(VectorOperators.MIN)} so the JIT emits the
+ * platform's float-min and float-min-reduce intrinsics. Null lanes preserve 
the lane's seeded
+ * {@link Float#POSITIVE_INFINITY} via masked {@code lanewise} so the 
reduction is unaffected by them.
+ */
+public final class SimdFloatMinVectorAggregator extends 
FloatMinVectorAggregator implements NullAwareVectorAggregator
+{
+  private static final VectorSpecies<Float> SPECIES = 
FloatVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdFloatMinVectorAggregator(VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(ByteBuffer buf, int position, int startRow, int endRow)
+  {
+    final float[] vector = selector.getFloatVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    FloatVector vacc = FloatVector.broadcast(SPECIES, Float.POSITIVE_INFINITY);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.min(FloatVector.fromArray(SPECIES, vector, i));
+    }
+    float localMin = vacc.reduceLanes(VectorOperators.MIN);
+    for (; i < endRow; i++) {
+      localMin = Math.min(localMin, vector[i]);
+    }
+    buf.putFloat(position, Math.min(buf.getFloat(position), localMin));
+  }
+
+  @Override
+  public boolean aggregate(ByteBuffer buf, int position, int startRow, int 
endRow, boolean[] nullVector)
+  {
+    final float[] vector = selector.getFloatVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    FloatVector vacc = FloatVector.broadcast(SPECIES, Float.POSITIVE_INFINITY);
+    int nonNullCount = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Float> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.lanewise(VectorOperators.MIN, FloatVector.fromArray(SPECIES, 
vector, i), notNull);
+      nonNullCount += notNull.trueCount();
+    }
+    float localMin = vacc.reduceLanes(VectorOperators.MIN);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        localMin = Math.min(localMin, vector[i]);
+        nonNullCount++;
+      }
+    }
+    if (nonNullCount > 0) {
+      buf.putFloat(position, Math.min(buf.getFloat(position), localMin));
+      return true;
+    }
+    return false;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdLongMaxVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdLongMaxVectorAggregator.java
new file mode 100644
index 00000000000..aad1edfc087
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdLongMaxVectorAggregator.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.query.aggregation.LongMaxVectorAggregator;
+import org.apache.druid.query.aggregation.NullAwareVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link LongMaxVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * issues a hardcoded {@link LongVector#max} and a {@code 
reduceLanes(VectorOperators.MAX)} so the JIT emits the
+ * platform's long-max and long-max-reduce intrinsics. Null lanes preserve the 
lane's seeded {@link Long#MIN_VALUE}
+ * via masked {@code lanewise} so the reduction is unaffected by them.
+ */
+public final class SimdLongMaxVectorAggregator extends LongMaxVectorAggregator 
implements NullAwareVectorAggregator
+{
+  private static final VectorSpecies<Long> SPECIES = 
LongVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdLongMaxVectorAggregator(VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(ByteBuffer buf, int position, int startRow, int endRow)
+  {
+    final long[] vector = selector.getLongVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    LongVector vacc = LongVector.broadcast(SPECIES, Long.MIN_VALUE);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.max(LongVector.fromArray(SPECIES, vector, i));
+    }
+    long localMax = vacc.reduceLanes(VectorOperators.MAX);
+    for (; i < endRow; i++) {
+      localMax = Math.max(localMax, vector[i]);
+    }
+    buf.putLong(position, Math.max(buf.getLong(position), localMax));
+  }
+
+  @Override
+  public boolean aggregate(ByteBuffer buf, int position, int startRow, int 
endRow, boolean[] nullVector)
+  {
+    final long[] vector = selector.getLongVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    LongVector vacc = LongVector.broadcast(SPECIES, Long.MIN_VALUE);
+    int nonNullCount = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Long> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.lanewise(VectorOperators.MAX, LongVector.fromArray(SPECIES, 
vector, i), notNull);
+      nonNullCount += notNull.trueCount();
+    }
+    long localMax = vacc.reduceLanes(VectorOperators.MAX);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        localMax = Math.max(localMax, vector[i]);
+        nonNullCount++;
+      }
+    }
+    if (nonNullCount > 0) {
+      buf.putLong(position, Math.max(buf.getLong(position), localMax));
+      return true;
+    }
+    return false;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdLongMinVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdLongMinVectorAggregator.java
new file mode 100644
index 00000000000..5d77a63a57d
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdLongMinVectorAggregator.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.query.aggregation.LongMinVectorAggregator;
+import org.apache.druid.query.aggregation.NullAwareVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link LongMinVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * issues a hardcoded {@link LongVector#min} and a {@code 
reduceLanes(VectorOperators.MIN)} so the JIT emits the
+ * platform's long-min and long-min-reduce intrinsics. Null lanes preserve the 
lane's seeded {@link Long#MAX_VALUE}
+ * via masked {@code lanewise} so the reduction is unaffected by them.
+ */
+public final class SimdLongMinVectorAggregator extends LongMinVectorAggregator 
implements NullAwareVectorAggregator
+{
+  private static final VectorSpecies<Long> SPECIES = 
LongVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdLongMinVectorAggregator(VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(ByteBuffer buf, int position, int startRow, int endRow)
+  {
+    final long[] vector = selector.getLongVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    LongVector vacc = LongVector.broadcast(SPECIES, Long.MAX_VALUE);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.min(LongVector.fromArray(SPECIES, vector, i));
+    }
+    long localMin = vacc.reduceLanes(VectorOperators.MIN);
+    for (; i < endRow; i++) {
+      localMin = Math.min(localMin, vector[i]);
+    }
+    buf.putLong(position, Math.min(buf.getLong(position), localMin));
+  }
+
+  @Override
+  public boolean aggregate(ByteBuffer buf, int position, int startRow, int 
endRow, boolean[] nullVector)
+  {
+    final long[] vector = selector.getLongVector();
+
+    final int laneCount = SPECIES.length();
+    final int upperBound = startRow + SPECIES.loopBound(endRow - startRow);
+    int i = startRow;
+    LongVector vacc = LongVector.broadcast(SPECIES, Long.MAX_VALUE);
+    int nonNullCount = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Long> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.lanewise(VectorOperators.MIN, LongVector.fromArray(SPECIES, 
vector, i), notNull);
+      nonNullCount += notNull.trueCount();
+    }
+    long localMin = vacc.reduceLanes(VectorOperators.MIN);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        localMin = Math.min(localMin, vector[i]);
+        nonNullCount++;
+      }
+    }
+    if (nonNullCount > 0) {
+      buf.putLong(position, Math.min(buf.getLong(position), localMin));
+      return true;
+    }
+    return false;
+  }
+}
diff --git 
a/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdAggregatorTestHelpers.java
 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdAggregatorTestHelpers.java
new file mode 100644
index 00000000000..d6bd0c735d1
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdAggregatorTestHelpers.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import javax.annotation.Nullable;
+import java.util.Random;
+import java.util.function.IntPredicate;
+
+/**
+ * Shared fixtures for the SIMD vector aggregator equivalence tests. Holds the 
vector-size axis, a set of null
+ * patterns designed to stress specific regions of the SIMD chunk loop, a 
minimal in-memory
+ * {@link VectorValueSelector}, and small pseudo-random data generators.
+ */
+final class SimdAggregatorTestHelpers
+{
+  /**
+   * Vector sizes picked to exercise distinct regimes against typical SIMD 
species lengths: 1 is tail-only,
+   * 8 fills an exact AVX2/AVX-512 chunk, 17 leaves a ragged tail after one or 
more chunks, 64 spans many chunks,
+   * and 1023 is large with a ragged tail.
+   */
+  static final int[] VECTOR_SIZES = {1, 8, 17, 64, 1023};
+
+  private SimdAggregatorTestHelpers()
+  {
+  }
+
+  /**
+   * Returns a {@code boolean[]} of length {@code startRow + realNulls.length} 
with the real null pattern copied
+   * to indices {@code [startRow, startRow + realNulls.length)}. The leading 
slots are left as {@code false}
+   * (non-null) so that if the aggregator incorrectly reads past {@code 
startRow} the poison values would slip in.
+   */
+  static boolean[] padNulls(boolean[] realNulls, int startRow)
+  {
+    final boolean[] padded = new boolean[startRow + realNulls.length];
+    System.arraycopy(realNulls, 0, padded, startRow, realNulls.length);
+    return padded;
+  }
+
+  static long[] randomLongs(int size, int seed)
+  {
+    final Random r = new Random(0xC0FFEEL + seed);
+    final long[] out = new long[size];
+    for (int i = 0; i < size; i++) {
+      out[i] = r.nextInt() & 0xFFFFFL;
+    }
+    return out;
+  }
+
+  static double[] randomDoubles(int size, int seed)
+  {
+    final Random r = new Random(0xC0FFEEL + seed);
+    final double[] out = new double[size];
+    for (int i = 0; i < size; i++) {
+      out[i] = (r.nextDouble() - 0.5) * 1000.0;
+    }
+    return out;
+  }
+
+  static float[] randomFloats(int size, int seed)
+  {
+    final Random r = new Random(0xC0FFEEL + seed);
+    final float[] out = new float[size];
+    for (int i = 0; i < size; i++) {
+      out[i] = (r.nextFloat() - 0.5f) * 1000.0f;
+    }
+    return out;
+  }
+
+  /**
+   * Null distributions designed to stress the SIMD null-aware path. {@link 
#NONE} returns {@code null} from
+   * {@link #toMask(int)} to model a column with no null vector at all (the 
common non-nullable case). The rest
+   * exercise sparse, dense, all-null, alternating, and 
chunk-boundary-adjacent placements.
+   */
+  enum NullPattern
+  {
+    NONE(i -> false),
+    ALL(i -> true),
+    ALTERNATING(i -> (i & 1) == 0),
+    SPARSE(i -> i % 7 == 0),
+    FIRST_THREE(i -> i < 3),
+    CHUNK_BOUNDARY(i -> i == 7 || i == 8);
+
+    private final IntPredicate predicate;
+
+    NullPattern(IntPredicate predicate)
+    {
+      this.predicate = predicate;
+    }
+
+    @Nullable
+    boolean[] toMask(int size)
+    {
+      if (this == NONE) {
+        return null;
+      }
+      final boolean[] mask = new boolean[size];
+      for (int i = 0; i < size; i++) {
+        mask[i] = predicate.test(i);
+      }
+      return mask;
+    }
+  }
+
+  /**
+   * Minimal in-memory {@link VectorValueSelector} backed by pre-built 
primitive arrays. Only the accessor for the
+   * type used by a given test is non-null; the others return whatever was 
passed at construction (typically null).
+   */
+  static final class FakeVectorValueSelector implements VectorValueSelector
+  {
+    private final int size;
+    @Nullable
+    private final long[] longs;
+    @Nullable
+    private final double[] doubles;
+    @Nullable
+    private final float[] floats;
+    @Nullable
+    private final boolean[] nulls;
+
+    FakeVectorValueSelector(
+        int size,
+        @Nullable long[] longs,
+        @Nullable double[] doubles,
+        @Nullable float[] floats,
+        @Nullable boolean[] nulls
+    )
+    {
+      this.size = size;
+      this.longs = longs;
+      this.doubles = doubles;
+      this.floats = floats;
+      this.nulls = nulls;
+    }
+
+    @Override
+    public long[] getLongVector()
+    {
+      return longs;
+    }
+
+    @Override
+    public float[] getFloatVector()
+    {
+      return floats;
+    }
+
+    @Override
+    public double[] getDoubleVector()
+    {
+      return doubles;
+    }
+
+    @Nullable
+    @Override
+    public boolean[] getNullVector()
+    {
+      return nulls;
+    }
+
+    @Override
+    public int getMaxVectorSize()
+    {
+      return size;
+    }
+
+    @Override
+    public int getCurrentVectorSize()
+    {
+      return size;
+    }
+  }
+}
diff --git 
a/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdMinMaxVectorAggregatorTest.java
 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdMinMaxVectorAggregatorTest.java
new file mode 100644
index 00000000000..256021b24ef
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdMinMaxVectorAggregatorTest.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.aggregation.simd;
+
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.query.aggregation.DoubleMaxVectorAggregator;
+import org.apache.druid.query.aggregation.DoubleMinVectorAggregator;
+import org.apache.druid.query.aggregation.FloatMaxVectorAggregator;
+import org.apache.druid.query.aggregation.FloatMinVectorAggregator;
+import org.apache.druid.query.aggregation.LongMaxVectorAggregator;
+import org.apache.druid.query.aggregation.LongMinVectorAggregator;
+import 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.FakeVectorValueSelector;
+import 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.NullPattern;
+import org.apache.druid.testing.InitializedNullHandlingTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.VECTOR_SIZES;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.padNulls;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.randomDoubles;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.randomFloats;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.randomLongs;
+
+/**
+ * Equivalence tests for SIMD Min/Max vector aggregators. For each (op, type, 
vector size, null pattern) tuple,
+ * drives the SIMD aggregator directly and compares against either the scalar 
parent (no-null path) or a manually
+ * computed reference (null-aware path). When every row is null the null-aware 
path must report false and leave
+ * the buffer's seeded identity value untouched.
+ *
+ * Each scenario is exercised twice: once with {@code (position=0, startRow=0, 
endRow=size)} and once with
+ * {@code (position=1, startRow=1, endRow=size+1)} where the row at index 0 
holds a deliberately op-defeating
+ * "poison" value (an extreme low for min, extreme high for max) that would 
visibly change the result if the
+ * aggregator incorrectly read past {@code startRow}, and the buffer slot 
starts at byte offset 1 so any
+ * indexing off the position parameter shows up.
+ */
+public class SimdMinMaxVectorAggregatorTest extends InitializedNullHandlingTest
+{
+  @Test
+  public void testLongMin()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern p : NullPattern.values()) {
+        runLong(size, p, true, 0, 0);
+        runLong(size, p, true, 1, 1);
+      }
+    }
+  }
+
+  @Test
+  public void testLongMax()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern p : NullPattern.values()) {
+        runLong(size, p, false, 0, 0);
+        runLong(size, p, false, 1, 1);
+      }
+    }
+  }
+
+  @Test
+  public void testDoubleMin()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern p : NullPattern.values()) {
+        runDouble(size, p, true, 0, 0);
+        runDouble(size, p, true, 1, 1);
+      }
+    }
+  }
+
+  @Test
+  public void testDoubleMax()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern p : NullPattern.values()) {
+        runDouble(size, p, false, 0, 0);
+        runDouble(size, p, false, 1, 1);
+      }
+    }
+  }
+
+  @Test
+  public void testFloatMin()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern p : NullPattern.values()) {
+        runFloat(size, p, true, 0, 0);
+        runFloat(size, p, true, 1, 1);
+      }
+    }
+  }
+
+  @Test
+  public void testFloatMax()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern p : NullPattern.values()) {
+        runFloat(size, p, false, 0, 0);
+        runFloat(size, p, false, 1, 1);
+      }
+    }
+  }
+
+  private static void runLong(int size, NullPattern pattern, boolean isMin, 
int position, int startRow)
+  {
+    final int arrLen = startRow + size;
+    final long[] values = new long[arrLen];
+    final long poison = isMin ? Long.MIN_VALUE : Long.MAX_VALUE;
+    for (int i = 0; i < startRow; i++) {
+      values[i] = poison;
+    }
+    System.arraycopy(randomLongs(size, isMin ? 0 : 1), 0, values, startRow, 
size);
+
+    final boolean[] realNulls = pattern.toMask(size);
+    final boolean[] nulls = realNulls == null ? null : padNulls(realNulls, 
startRow);
+
+    final FakeVectorValueSelector selector = new 
FakeVectorValueSelector(arrLen, values, null, null, nulls);
+    final int endRow = startRow + size;
+    final String msg = StringUtils.format(
+        "type[long] op[%s] size[%s] nulls[%s] pos[%s] start[%s]",
+        isMin ? "min" : "max", size, pattern, position, startRow
+    );
+    final long identity = isMin ? Long.MAX_VALUE : Long.MIN_VALUE;
+
+    if (nulls == null) {
+      final ByteBuffer scalarBuf = ByteBuffer.allocate(position + Long.BYTES);
+      final ByteBuffer simdBuf = ByteBuffer.allocate(position + Long.BYTES);
+      if (isMin) {
+        final LongMinVectorAggregator scalar = new 
LongMinVectorAggregator(selector);
+        final SimdLongMinVectorAggregator simd = new 
SimdLongMinVectorAggregator(selector);
+        scalar.init(scalarBuf, position);
+        simd.init(simdBuf, position);
+        scalar.aggregate(scalarBuf, position, startRow, endRow);
+        simd.aggregate(simdBuf, position, startRow, endRow);
+      } else {
+        final LongMaxVectorAggregator scalar = new 
LongMaxVectorAggregator(selector);
+        final SimdLongMaxVectorAggregator simd = new 
SimdLongMaxVectorAggregator(selector);
+        scalar.init(scalarBuf, position);
+        simd.init(simdBuf, position);
+        scalar.aggregate(scalarBuf, position, startRow, endRow);
+        simd.aggregate(simdBuf, position, startRow, endRow);
+      }
+      Assert.assertEquals(msg, scalarBuf.getLong(position), 
simdBuf.getLong(position));
+    } else {
+      long expected = identity;
+      boolean anyNonNull = false;
+      for (int i = startRow; i < endRow; i++) {
+        if (!nulls[i]) {
+          expected = isMin ? Math.min(expected, values[i]) : 
Math.max(expected, values[i]);
+          anyNonNull = true;
+        }
+      }
+      final ByteBuffer simdBuf = ByteBuffer.allocate(position + Long.BYTES);
+      final boolean reported;
+      if (isMin) {
+        final SimdLongMinVectorAggregator simd = new 
SimdLongMinVectorAggregator(selector);
+        simd.init(simdBuf, position);
+        reported = simd.aggregate(simdBuf, position, startRow, endRow, nulls);
+      } else {
+        final SimdLongMaxVectorAggregator simd = new 
SimdLongMaxVectorAggregator(selector);
+        simd.init(simdBuf, position);
+        reported = simd.aggregate(simdBuf, position, startRow, endRow, nulls);
+      }
+      Assert.assertEquals(msg + " (anyNonNull)", anyNonNull, reported);
+      Assert.assertEquals(msg, expected, simdBuf.getLong(position));
+    }
+  }
+
+  private static void runDouble(int size, NullPattern pattern, boolean isMin, 
int position, int startRow)
+  {
+    final int arrLen = startRow + size;
+    final double[] values = new double[arrLen];
+    final double poison = isMin ? Double.NEGATIVE_INFINITY : 
Double.POSITIVE_INFINITY;
+    for (int i = 0; i < startRow; i++) {
+      values[i] = poison;
+    }
+    System.arraycopy(randomDoubles(size, isMin ? 2 : 3), 0, values, startRow, 
size);
+
+    final boolean[] realNulls = pattern.toMask(size);
+    final boolean[] nulls = realNulls == null ? null : padNulls(realNulls, 
startRow);
+
+    final FakeVectorValueSelector selector = new 
FakeVectorValueSelector(arrLen, null, values, null, nulls);
+    final int endRow = startRow + size;
+    final String msg = StringUtils.format(
+        "type[double] op[%s] size[%s] nulls[%s] pos[%s] start[%s]",
+        isMin ? "min" : "max", size, pattern, position, startRow
+    );
+    final double identity = isMin ? Double.POSITIVE_INFINITY : 
Double.NEGATIVE_INFINITY;
+
+    if (nulls == null) {
+      final ByteBuffer scalarBuf = ByteBuffer.allocate(position + 
Double.BYTES);
+      final ByteBuffer simdBuf = ByteBuffer.allocate(position + Double.BYTES);
+      if (isMin) {
+        final DoubleMinVectorAggregator scalar = new 
DoubleMinVectorAggregator(selector);
+        final SimdDoubleMinVectorAggregator simd = new 
SimdDoubleMinVectorAggregator(selector);
+        scalar.init(scalarBuf, position);
+        simd.init(simdBuf, position);
+        scalar.aggregate(scalarBuf, position, startRow, endRow);
+        simd.aggregate(simdBuf, position, startRow, endRow);
+      } else {
+        final DoubleMaxVectorAggregator scalar = new 
DoubleMaxVectorAggregator(selector);
+        final SimdDoubleMaxVectorAggregator simd = new 
SimdDoubleMaxVectorAggregator(selector);
+        scalar.init(scalarBuf, position);
+        simd.init(simdBuf, position);
+        scalar.aggregate(scalarBuf, position, startRow, endRow);
+        simd.aggregate(simdBuf, position, startRow, endRow);
+      }
+      // min/max produces a value that was present in the input -- exact 
equality is fine.
+      Assert.assertEquals(msg, scalarBuf.getDouble(position), 
simdBuf.getDouble(position), 0.0);
+    } else {
+      double expected = identity;
+      boolean anyNonNull = false;
+      for (int i = startRow; i < endRow; i++) {
+        if (!nulls[i]) {
+          expected = isMin ? Math.min(expected, values[i]) : 
Math.max(expected, values[i]);
+          anyNonNull = true;
+        }
+      }
+      final ByteBuffer simdBuf = ByteBuffer.allocate(position + Double.BYTES);
+      final boolean reported;
+      if (isMin) {
+        final SimdDoubleMinVectorAggregator simd = new 
SimdDoubleMinVectorAggregator(selector);
+        simd.init(simdBuf, position);
+        reported = simd.aggregate(simdBuf, position, startRow, endRow, nulls);
+      } else {
+        final SimdDoubleMaxVectorAggregator simd = new 
SimdDoubleMaxVectorAggregator(selector);
+        simd.init(simdBuf, position);
+        reported = simd.aggregate(simdBuf, position, startRow, endRow, nulls);
+      }
+      Assert.assertEquals(msg + " (anyNonNull)", anyNonNull, reported);
+      Assert.assertEquals(msg, expected, simdBuf.getDouble(position), 0.0);
+    }
+  }
+
+  private static void runFloat(int size, NullPattern pattern, boolean isMin, 
int position, int startRow)
+  {
+    final int arrLen = startRow + size;
+    final float[] values = new float[arrLen];
+    final float poison = isMin ? Float.NEGATIVE_INFINITY : 
Float.POSITIVE_INFINITY;
+    for (int i = 0; i < startRow; i++) {
+      values[i] = poison;
+    }
+    System.arraycopy(randomFloats(size, isMin ? 4 : 5), 0, values, startRow, 
size);
+
+    final boolean[] realNulls = pattern.toMask(size);
+    final boolean[] nulls = realNulls == null ? null : padNulls(realNulls, 
startRow);
+
+    final FakeVectorValueSelector selector = new 
FakeVectorValueSelector(arrLen, null, null, values, nulls);
+    final int endRow = startRow + size;
+    final String msg = StringUtils.format(
+        "type[float] op[%s] size[%s] nulls[%s] pos[%s] start[%s]",
+        isMin ? "min" : "max", size, pattern, position, startRow
+    );
+    final float identity = isMin ? Float.POSITIVE_INFINITY : 
Float.NEGATIVE_INFINITY;
+
+    if (nulls == null) {
+      final ByteBuffer scalarBuf = ByteBuffer.allocate(position + Float.BYTES);
+      final ByteBuffer simdBuf = ByteBuffer.allocate(position + Float.BYTES);
+      if (isMin) {
+        final FloatMinVectorAggregator scalar = new 
FloatMinVectorAggregator(selector);
+        final SimdFloatMinVectorAggregator simd = new 
SimdFloatMinVectorAggregator(selector);
+        scalar.init(scalarBuf, position);
+        simd.init(simdBuf, position);
+        scalar.aggregate(scalarBuf, position, startRow, endRow);
+        simd.aggregate(simdBuf, position, startRow, endRow);
+      } else {
+        final FloatMaxVectorAggregator scalar = new 
FloatMaxVectorAggregator(selector);
+        final SimdFloatMaxVectorAggregator simd = new 
SimdFloatMaxVectorAggregator(selector);
+        scalar.init(scalarBuf, position);
+        simd.init(simdBuf, position);
+        scalar.aggregate(scalarBuf, position, startRow, endRow);
+        simd.aggregate(simdBuf, position, startRow, endRow);
+      }
+      Assert.assertEquals(msg, scalarBuf.getFloat(position), 
simdBuf.getFloat(position), 0.0f);
+    } else {
+      float expected = identity;
+      boolean anyNonNull = false;
+      for (int i = startRow; i < endRow; i++) {
+        if (!nulls[i]) {
+          expected = isMin ? Math.min(expected, values[i]) : 
Math.max(expected, values[i]);
+          anyNonNull = true;
+        }
+      }
+      final ByteBuffer simdBuf = ByteBuffer.allocate(position + Float.BYTES);
+      final boolean reported;
+      if (isMin) {
+        final SimdFloatMinVectorAggregator simd = new 
SimdFloatMinVectorAggregator(selector);
+        simd.init(simdBuf, position);
+        reported = simd.aggregate(simdBuf, position, startRow, endRow, nulls);
+      } else {
+        final SimdFloatMaxVectorAggregator simd = new 
SimdFloatMaxVectorAggregator(selector);
+        simd.init(simdBuf, position);
+        reported = simd.aggregate(simdBuf, position, startRow, endRow, nulls);
+      }
+      Assert.assertEquals(msg + " (anyNonNull)", anyNonNull, reported);
+      Assert.assertEquals(msg, expected, simdBuf.getFloat(position), 0.0f);
+    }
+  }
+
+}
diff --git 
a/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdSumVectorAggregatorTest.java
 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdSumVectorAggregatorTest.java
index 19e54b075a3..d4fbd27f399 100644
--- 
a/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdSumVectorAggregatorTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdSumVectorAggregatorTest.java
@@ -23,15 +23,19 @@ import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.query.aggregation.DoubleSumVectorAggregator;
 import org.apache.druid.query.aggregation.FloatSumVectorAggregator;
 import org.apache.druid.query.aggregation.LongSumVectorAggregator;
-import org.apache.druid.segment.vector.VectorValueSelector;
+import 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.FakeVectorValueSelector;
+import 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.NullPattern;
 import org.apache.druid.testing.InitializedNullHandlingTest;
 import org.junit.Assert;
 import org.junit.Test;
 
-import javax.annotation.Nullable;
 import java.nio.ByteBuffer;
-import java.util.Random;
-import java.util.function.IntPredicate;
+
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.VECTOR_SIZES;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.padNulls;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.randomDoubles;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.randomFloats;
+import static 
org.apache.druid.query.aggregation.simd.SimdAggregatorTestHelpers.randomLongs;
 
 /**
  * For each (sum type, vector size, null pattern) combination, drives the SIMD 
and scalar sum vector aggregators
@@ -49,7 +53,6 @@ import java.util.function.IntPredicate;
  */
 public class SimdSumVectorAggregatorTest extends InitializedNullHandlingTest
 {
-  private static final int[] VECTOR_SIZES = {1, 8, 17, 64, 1023};
   private static final long POISON_LONG = Long.MAX_VALUE / 2;
   private static final double POISON_DOUBLE = 1e15;
   private static final float POISON_FLOAT = 1e10f;
@@ -254,139 +257,4 @@ public class SimdSumVectorAggregatorTest extends 
InitializedNullHandlingTest
     }
   }
 
-  private static boolean[] padNulls(boolean[] realNulls, int startRow)
-  {
-    final boolean[] padded = new boolean[startRow + realNulls.length];
-    System.arraycopy(realNulls, 0, padded, startRow, realNulls.length);
-    return padded;
-  }
-
-  private static long[] randomLongs(int size, int seed)
-  {
-    final Random r = new Random(0xC0FFEEL + seed);
-    final long[] out = new long[size];
-    for (int i = 0; i < size; i++) {
-      out[i] = r.nextInt() & 0xFFFFFL;
-    }
-    return out;
-  }
-
-  private static double[] randomDoubles(int size, int seed)
-  {
-    final Random r = new Random(0xC0FFEEL + seed);
-    final double[] out = new double[size];
-    for (int i = 0; i < size; i++) {
-      out[i] = (r.nextDouble() - 0.5) * 1000.0;
-    }
-    return out;
-  }
-
-  private static float[] randomFloats(int size, int seed)
-  {
-    final Random r = new Random(0xC0FFEEL + seed);
-    final float[] out = new float[size];
-    for (int i = 0; i < size; i++) {
-      out[i] = (r.nextFloat() - 0.5f) * 1000.0f;
-    }
-    return out;
-  }
-
-  private enum NullPattern
-  {
-    NONE(i -> false),
-    ALL(i -> true),
-    ALTERNATING(i -> (i & 1) == 0),
-    SPARSE(i -> i % 7 == 0),
-    FIRST_THREE(i -> i < 3),
-    CHUNK_BOUNDARY(i -> i == 7 || i == 8);
-
-    private final IntPredicate predicate;
-
-    NullPattern(IntPredicate predicate)
-    {
-      this.predicate = predicate;
-    }
-
-    @Nullable
-    boolean[] toMask(int size)
-    {
-      if (this == NONE) {
-        return null;        // models a column with no null vector at all
-      }
-      final boolean[] mask = new boolean[size];
-      for (int i = 0; i < size; i++) {
-        mask[i] = predicate.test(i);
-      }
-      return mask;
-    }
-  }
-
-  /**
-   * Minimal in-memory {@link VectorValueSelector} backed by pre-built 
primitive arrays for tests. Only the
-   * accessor for the type used by a given test is non-null.
-   */
-  private static final class FakeVectorValueSelector implements 
VectorValueSelector
-  {
-    private final int size;
-    @Nullable
-    private final long[] longs;
-    @Nullable
-    private final double[] doubles;
-    @Nullable
-    private final float[] floats;
-    @Nullable
-    private final boolean[] nulls;
-
-    FakeVectorValueSelector(
-        int size,
-        @Nullable long[] longs,
-        @Nullable double[] doubles,
-        @Nullable float[] floats,
-        @Nullable boolean[] nulls
-    )
-    {
-      this.size = size;
-      this.longs = longs;
-      this.doubles = doubles;
-      this.floats = floats;
-      this.nulls = nulls;
-    }
-
-    @Override
-    public long[] getLongVector()
-    {
-      return longs;
-    }
-
-    @Override
-    public float[] getFloatVector()
-    {
-      return floats;
-    }
-
-    @Override
-    public double[] getDoubleVector()
-    {
-      return doubles;
-    }
-
-    @Nullable
-    @Override
-    public boolean[] getNullVector()
-    {
-      return nulls;
-    }
-
-    @Override
-    public int getMaxVectorSize()
-    {
-      return size;
-    }
-
-    @Override
-    public int getCurrentVectorSize()
-    {
-      return size;
-    }
-  }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionResultConsistencyTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionResultConsistencyTest.java
index 0ea81fc1c11..75157793dd0 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionResultConsistencyTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionResultConsistencyTest.java
@@ -225,8 +225,8 @@ public class SqlVectorizedExpressionResultConsistencyTest 
extends InitializedNul
                       nonVectorObject,
                       vectorObject
                   ),
-                  ((Double) nonVectorObject).doubleValue(),
-                  ((Double) vectorObject).doubleValue(),
+                  ((Number) nonVectorObject).doubleValue(),
+                  ((Number) vectorObject).doubleValue(),
                   0.01
               );
             } else {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to