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

jtuglu1 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 5b4755e942b perf: add SIMD acceleration for doubleMean aggregator 
(#19681)
5b4755e942b is described below

commit 5b4755e942b079b86421d639508593bd81f499e7
Author: jtuglu1 <[email protected]>
AuthorDate: Tue Jul 14 22:31:42 2026 -0700

    perf: add SIMD acceleration for doubleMean aggregator (#19681)
    
    Adds SIMD acceleration for the doubleMean aggregator. Opt-in via the 
useVectorApi flag.
---
 .../DoubleMeanVectorAggregatorBenchmark.java       | 179 +++++++++++++++++++++
 .../mean/DoubleMeanAggregatorFactory.java          |   9 +-
 .../query/aggregation/mean/DoubleMeanHolder.java   |   6 +
 .../simd/SimdDoubleMeanVectorAggregator.java       | 114 +++++++++++++
 .../mean/DoubleMeanAggregationTest.java            |  59 +++++--
 .../simd/SimdDoubleMeanVectorAggregatorTest.java   | 136 ++++++++++++++++
 6 files changed, 491 insertions(+), 12 deletions(-)

diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/DoubleMeanVectorAggregatorBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/DoubleMeanVectorAggregatorBenchmark.java
new file mode 100644
index 00000000000..65000ee2ea2
--- /dev/null
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/DoubleMeanVectorAggregatorBenchmark.java
@@ -0,0 +1,179 @@
+/*
+ * 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.benchmark;
+
+import org.apache.druid.query.aggregation.VectorAggregator;
+import org.apache.druid.query.aggregation.mean.DoubleMeanHolder;
+import org.apache.druid.query.aggregation.mean.DoubleMeanVectorAggregator;
+import org.apache.druid.query.aggregation.simd.SimdDoubleMeanVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+import javax.annotation.Nullable;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+@State(Scope.Benchmark)
+@Fork(value = 1, jvmArgsAppend = "--add-modules=jdk.incubator.vector")
+@Warmup(iterations = 5)
+@Measurement(iterations = 5)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+public class DoubleMeanVectorAggregatorBenchmark
+{
+  @Param({"128", "512", "1024", "4096"})
+  private int vectorSize;
+
+  @Param({"none", "sparse", "alternating"})
+  private String nullPattern;
+
+  private ByteBuffer scalarBuffer;
+  private ByteBuffer simdBuffer;
+  private VectorAggregator scalarAggregator;
+  private VectorAggregator simdAggregator;
+
+  @Setup(Level.Trial)
+  public void setup()
+  {
+    final double[] values = new double[vectorSize];
+    final Random random = new Random(0xC0FFEEL);
+    for (int i = 0; i < vectorSize; i++) {
+      values[i] = (random.nextDouble() - 0.5) * 1000.0;
+    }
+
+    final boolean[] nulls = makeNullVector();
+    final VectorValueSelector selector = new 
FakeVectorValueSelector(vectorSize, values, nulls);
+    scalarAggregator = new DoubleMeanVectorAggregator(selector);
+    simdAggregator = new SimdDoubleMeanVectorAggregator(selector);
+    scalarBuffer = ByteBuffer.allocate(DoubleMeanHolder.MAX_INTERMEDIATE_SIZE);
+    simdBuffer = ByteBuffer.allocate(DoubleMeanHolder.MAX_INTERMEDIATE_SIZE);
+  }
+
+  @Setup(Level.Invocation)
+  public void setupInvocation()
+  {
+    scalarAggregator.init(scalarBuffer, 0);
+    simdAggregator.init(simdBuffer, 0);
+  }
+
+  @Benchmark
+  public void scalarDoubleMean(final Blackhole blackhole)
+  {
+    scalarAggregator.aggregate(scalarBuffer, 0, 0, vectorSize);
+    blackhole.consume(scalarBuffer.getDouble(0));
+    blackhole.consume(scalarBuffer.getLong(Double.BYTES));
+  }
+
+  @Benchmark
+  public void simdDoubleMean(final Blackhole blackhole)
+  {
+    simdAggregator.aggregate(simdBuffer, 0, 0, vectorSize);
+    blackhole.consume(simdBuffer.getDouble(0));
+    blackhole.consume(simdBuffer.getLong(Double.BYTES));
+  }
+
+  @Nullable
+  private boolean[] makeNullVector()
+  {
+    return switch (nullPattern) {
+      case "none" -> null;
+      case "sparse" -> {
+        final boolean[] nulls = new boolean[vectorSize];
+        for (int i = 0; i < vectorSize; i++) {
+          nulls[i] = i % 17 == 0;
+        }
+        yield nulls;
+      }
+      case "alternating" -> {
+        final boolean[] nulls = new boolean[vectorSize];
+        for (int i = 0; i < vectorSize; i++) {
+          nulls[i] = (i & 1) == 0;
+        }
+        yield nulls;
+      }
+      default -> throw new IllegalStateException("Unsupported null pattern[" + 
nullPattern + "]");
+    };
+  }
+
+  private static final class FakeVectorValueSelector implements 
VectorValueSelector
+  {
+    private final int size;
+    private final double[] doubles;
+    @Nullable
+    private final boolean[] nulls;
+
+    FakeVectorValueSelector(final int size, final double[] doubles, @Nullable 
final boolean[] nulls)
+    {
+      this.size = size;
+      this.doubles = doubles;
+      this.nulls = nulls;
+    }
+
+    @Override
+    public long[] getLongVector()
+    {
+      return null;
+    }
+
+    @Override
+    public float[] getFloatVector()
+    {
+      return null;
+    }
+
+    @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/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregatorFactory.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregatorFactory.java
index 58cbd502fef..eb81d496c20 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregatorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregatorFactory.java
@@ -25,17 +25,20 @@ import com.google.common.base.Preconditions;
 import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.common.collect.Utils;
+import org.apache.druid.math.expr.ExpressionProcessing;
 import org.apache.druid.query.aggregation.Aggregator;
 import org.apache.druid.query.aggregation.AggregatorFactory;
 import org.apache.druid.query.aggregation.AggregatorUtil;
 import org.apache.druid.query.aggregation.BufferAggregator;
 import org.apache.druid.query.aggregation.VectorAggregator;
+import org.apache.druid.query.aggregation.simd.SimdDoubleMeanVectorAggregator;
 import org.apache.druid.query.cache.CacheKeyBuilder;
 import org.apache.druid.segment.ColumnInspector;
 import org.apache.druid.segment.ColumnSelectorFactory;
 import org.apache.druid.segment.column.ColumnCapabilities;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.vector.VectorColumnSelectorFactory;
+import org.apache.druid.segment.vector.VectorValueSelector;
 
 import javax.annotation.Nullable;
 import java.util.Collections;
@@ -122,7 +125,11 @@ public class DoubleMeanAggregatorFactory extends 
AggregatorFactory
   @Override
   public VectorAggregator factorizeVector(final VectorColumnSelectorFactory 
selectorFactory)
   {
-    return new 
DoubleMeanVectorAggregator(selectorFactory.makeValueSelector(fieldName));
+    final VectorValueSelector selector = 
selectorFactory.makeValueSelector(fieldName);
+    if (ExpressionProcessing.useVectorApi()) {
+      return new SimdDoubleMeanVectorAggregator(selector);
+    }
+    return new DoubleMeanVectorAggregator(selector);
   }
 
   @Override
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanHolder.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanHolder.java
index f04150fcede..5b3d191cc6b 100644
--- 
a/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanHolder.java
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/mean/DoubleMeanHolder.java
@@ -107,6 +107,12 @@ public class DoubleMeanHolder
     writeCount(buf, position, getCount(buf, position) + 1);
   }
 
+  public static void update(final ByteBuffer buf, final int position, final 
double sum, final long count)
+  {
+    writeSum(buf, position, getSum(buf, position) + sum);
+    writeCount(buf, position, getCount(buf, position) + count);
+  }
+
   public static void update(ByteBuffer buf, int position, DoubleMeanHolder 
other)
   {
     writeSum(buf, position, getSum(buf, position) + other.sum);
diff --git 
a/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMeanVectorAggregator.java
 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMeanVectorAggregator.java
new file mode 100644
index 00000000000..ec62a468d30
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/aggregation/simd/SimdDoubleMeanVectorAggregator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.mean.DoubleMeanHolder;
+import org.apache.druid.query.aggregation.mean.DoubleMeanVectorAggregator;
+import org.apache.druid.segment.vector.VectorValueSelector;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SIMD specialization of {@link DoubleMeanVectorAggregator}'s ungrouped 
contiguous-range aggregation. The hot loop
+ * reduces input values into one local sum and count, then updates the mean 
holder once. The grouped scatter-gather
+ * variant is inherited from the parent scalar class.
+ *
+ * Null handling is done internally: {@code DoubleMeanAggregatorFactory} is 
not a {@code NullableNumericAggregatorFactory},
+ * so this aggregator is never wrapped by {@code 
NullableNumericVectorAggregator} and does not implement
+ * {@code NullAwareVectorAggregator}. The contiguous-range {@link 
#aggregate(ByteBuffer, int, int, int)} entry point
+ * inspects the selector's null vector itself and dispatches to a masked or 
unmasked SIMD loop.
+ */
+public final class SimdDoubleMeanVectorAggregator extends 
DoubleMeanVectorAggregator
+{
+  private static final VectorSpecies<Double> SPECIES = 
DoubleVector.SPECIES_PREFERRED;
+
+  private final VectorValueSelector selector;
+
+  public SimdDoubleMeanVectorAggregator(final VectorValueSelector selector)
+  {
+    super(selector);
+    this.selector = selector;
+  }
+
+  @Override
+  public void aggregate(final ByteBuffer buf, final int position, final int 
startRow, final int endRow)
+  {
+    final boolean[] nullVector = selector.getNullVector();
+    if (nullVector == null) {
+      aggregateNoNulls(buf, position, startRow, endRow);
+    } else {
+      aggregateNulls(buf, position, startRow, endRow, nullVector);
+    }
+  }
+
+  private void aggregateNulls(
+      final ByteBuffer buf,
+      final int position,
+      final int startRow,
+      final int endRow,
+      final 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.zero(SPECIES);
+    long count = 0;
+    for (; i < upperBound; i += laneCount) {
+      final VectorMask<Double> notNull = VectorMask.fromArray(SPECIES, 
nullVector, i).not();
+      vacc = vacc.add(DoubleVector.fromArray(SPECIES, vector, i), notNull);
+      count += notNull.trueCount();
+    }
+    double sum = vacc.reduceLanes(VectorOperators.ADD);
+    for (; i < endRow; i++) {
+      if (!nullVector[i]) {
+        sum += vector[i];
+        count++;
+      }
+    }
+    if (count > 0) {
+      DoubleMeanHolder.update(buf, position, sum, count);
+    }
+  }
+
+  private void aggregateNoNulls(final ByteBuffer buf, final int position, 
final int startRow, final 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.zero(SPECIES);
+    for (; i < upperBound; i += laneCount) {
+      vacc = vacc.add(DoubleVector.fromArray(SPECIES, vector, i));
+    }
+    double sum = vacc.reduceLanes(VectorOperators.ADD);
+    for (; i < endRow; i++) {
+      sum += vector[i];
+    }
+    DoubleMeanHolder.update(buf, position, sum, endRow - startRow);
+  }
+}
diff --git 
a/processing/src/test/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregationTest.java
 
b/processing/src/test/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregationTest.java
index 4c46129f78b..56330aef6e5 100644
--- 
a/processing/src/test/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregationTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/aggregation/mean/DoubleMeanAggregationTest.java
@@ -23,12 +23,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 import org.apache.druid.data.input.Row;
 import org.apache.druid.java.util.common.granularity.Granularities;
 import org.apache.druid.java.util.common.guava.Sequence;
+import org.apache.druid.math.expr.ExpressionProcessing;
 import org.apache.druid.query.Druids;
 import org.apache.druid.query.Query;
 import org.apache.druid.query.QueryContexts;
@@ -49,6 +49,7 @@ import org.apache.druid.segment.Segment;
 import org.apache.druid.segment.TestIndex;
 import org.apache.druid.timeline.SegmentId;
 import org.easymock.EasyMock;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -61,9 +62,13 @@ import java.util.List;
 @RunWith(JUnitParamsRunner.class)
 public class DoubleMeanAggregationTest
 {
-  public Object[] doVectorize()
+  public Object[] doVectorizeAndUseVectorApi()
   {
-    return Lists.newArrayList(true, false).toArray();
+    return new Object[]{
+        new Object[]{false, false},
+        new Object[]{true, false},
+        new Object[]{true, true}
+    };
   }
 
   @Rule
@@ -100,10 +105,19 @@ public class DoubleMeanAggregationTest
     );
   }
 
+  @After
+  public void resetExpressionProcessing()
+  {
+    ExpressionProcessing.initializeForTests();
+  }
+
   @Test
-  @Parameters(method = "doVectorize")
-  public void testBufferAggretatorUsingGroupByQuery(boolean doVectorize) 
throws Exception
+  @Parameters(method = "doVectorizeAndUseVectorApi")
+  public void testBufferAggretatorUsingGroupByQuery(final boolean doVectorize, 
final boolean useVectorApi)
+      throws Exception
   {
+    initializeExpressionProcessing(useVectorApi);
+
     GroupByQuery query = new GroupByQuery.Builder()
         .setDataSource("test")
         .setGranularity(Granularities.ALL)
@@ -129,9 +143,15 @@ public class DoubleMeanAggregationTest
   }
 
   @Test
-  @Parameters(method = "doVectorize")
-  public void testVectorAggretatorUsingGroupByQueryOnDoubleColumn(boolean 
doVectorize) throws Exception
+  @Parameters(method = "doVectorizeAndUseVectorApi")
+  public void testVectorAggretatorUsingGroupByQueryOnDoubleColumn(
+      final boolean doVectorize,
+      final boolean useVectorApi
+  )
+      throws Exception
   {
+    initializeExpressionProcessing(useVectorApi);
+
     GroupByQuery query = new GroupByQuery.Builder()
         .setDataSource("test")
         .setGranularity(Granularities.ALL)
@@ -153,9 +173,14 @@ public class DoubleMeanAggregationTest
   }
 
   @Test
-  @Parameters(method = "doVectorize")
-  public void 
testVectorAggretatorUsingGroupByQueryOnDoubleColumnOnBiggerSegments(boolean 
doVectorize) throws Exception
+  @Parameters(method = "doVectorizeAndUseVectorApi")
+  public void 
testVectorAggretatorUsingGroupByQueryOnDoubleColumnOnBiggerSegments(
+      final boolean doVectorize,
+      final boolean useVectorApi
+  ) throws Exception
   {
+    initializeExpressionProcessing(useVectorApi);
+
     GroupByQuery query = new GroupByQuery.Builder()
         .setDataSource("blah")
         .setGranularity(Granularities.ALL)
@@ -176,9 +201,12 @@ public class DoubleMeanAggregationTest
   }
 
   @Test
-  @Parameters(method = "doVectorize")
-  public void testAggretatorUsingTimeseriesQuery(boolean doVectorize) throws 
Exception
+  @Parameters(method = "doVectorizeAndUseVectorApi")
+  public void testAggretatorUsingTimeseriesQuery(final boolean doVectorize, 
final boolean useVectorApi)
+      throws Exception
   {
+    initializeExpressionProcessing(useVectorApi);
+
     TimeseriesQuery query = Druids.newTimeseriesQueryBuilder()
                                   .dataSource("test")
                                   .granularity(Granularities.ALL)
@@ -238,4 +266,13 @@ public class DoubleMeanAggregationTest
     DoubleMeanHolder meanHolder = (DoubleMeanHolder) aggregator.get();
     Assert.assertEquals(2.0, meanHolder.mean(), 0.0);
   }
+
+  private static void initializeExpressionProcessing(final boolean 
useVectorApi)
+  {
+    if (useVectorApi) {
+      ExpressionProcessing.initializeForVectorApiTests();
+    } else {
+      ExpressionProcessing.initializeForTests();
+    }
+  }
 }
diff --git 
a/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdDoubleMeanVectorAggregatorTest.java
 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdDoubleMeanVectorAggregatorTest.java
new file mode 100644
index 00000000000..2a9bcc12e42
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/query/aggregation/simd/SimdDoubleMeanVectorAggregatorTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.mean.DoubleMeanHolder;
+import org.apache.druid.query.aggregation.mean.DoubleMeanVectorAggregator;
+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;
+
+/**
+ * Equivalence test for the SIMD doubleMean vector aggregator. For each 
(vector size, null pattern) tuple it drives
+ * both the SIMD aggregator and the scalar parent over the same input and 
asserts the resulting mean holders match.
+ * The SIMD aggregator handles nulls internally (it is not a {@code 
NullAwareVectorAggregator}), so the contiguous
+ * {@code aggregate} entry point covers both the masked and unmasked loops.
+ *
+ * Each scenario is exercised twice: once with {@code (position=0, 
startRow=0)} and once with
+ * {@code (position=1, startRow=1)} where the rows before {@code startRow} 
hold a large "poison" value that would
+ * visibly skew the mean 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 SimdDoubleMeanVectorAggregatorTest extends 
InitializedNullHandlingTest
+{
+  private static final double POISON_DOUBLE = 1e15;
+  private static final double DELTA = 1e-9;
+
+  @Test
+  public void testDoubleMean()
+  {
+    for (int size : VECTOR_SIZES) {
+      for (NullPattern pattern : NullPattern.values()) {
+        runDoubleMean(size, pattern, 0, 0);
+        runDoubleMean(size, pattern, 1, 1);
+      }
+    }
+  }
+
+  private static void runDoubleMean(
+      final int size,
+      final NullPattern pattern,
+      final int position,
+      final int startRow
+  )
+  {
+    final int arrLen = startRow + size;
+    final double[] values = new double[arrLen];
+    for (int i = 0; i < startRow; i++) {
+      values[i] = POISON_DOUBLE;
+    }
+    System.arraycopy(randomDoubles(size, 0), 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(
+        "size[%s] nulls[%s] pos[%s] start[%s]",
+        size,
+        pattern,
+        position,
+        startRow
+    );
+
+    final DoubleMeanVectorAggregator scalar = new 
DoubleMeanVectorAggregator(selector);
+    final SimdDoubleMeanVectorAggregator simd = new 
SimdDoubleMeanVectorAggregator(selector);
+    final ByteBuffer scalarBuf = ByteBuffer.allocate(position + 
DoubleMeanHolder.MAX_INTERMEDIATE_SIZE);
+    final ByteBuffer simdBuf = ByteBuffer.allocate(position + 
DoubleMeanHolder.MAX_INTERMEDIATE_SIZE);
+    scalar.init(scalarBuf, position);
+    simd.init(simdBuf, position);
+
+    scalar.aggregate(scalarBuf, position, startRow, endRow);
+    simd.aggregate(simdBuf, position, startRow, endRow);
+    assertMeanHolderEquals(msg, scalarBuf, simdBuf, position);
+  }
+
+  private static void assertMeanHolderEquals(
+      final String msg,
+      final ByteBuffer expectedBuf,
+      final ByteBuffer actualBuf,
+      final int position
+  )
+  {
+    final DoubleMeanHolder expected = DoubleMeanHolder.get(expectedBuf, 
position);
+    final DoubleMeanHolder actual = DoubleMeanHolder.get(actualBuf, position);
+    Assert.assertEquals(msg + " (count)", count(expected), count(actual));
+    Assert.assertEquals(
+        msg + " (sum)",
+        sum(expected),
+        sum(actual),
+        Math.max(Math.abs(sum(expected)) * 1e-12, DELTA)
+    );
+    Assert.assertEquals(
+        msg + " (mean)",
+        expected.mean(),
+        actual.mean(),
+        Math.max(Math.abs(expected.mean()) * 1e-12, DELTA)
+    );
+  }
+
+  private static double sum(final DoubleMeanHolder holder)
+  {
+    return ByteBuffer.wrap(holder.toBytes()).getDouble(0);
+  }
+
+  private static long count(final DoubleMeanHolder holder)
+  {
+    return ByteBuffer.wrap(holder.toBytes()).getLong(Double.BYTES);
+  }
+}


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

Reply via email to