diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionMinMaxVectorProcessorBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionMinMaxVectorProcessorBenchmark.java new file mode 100644 index 000000000000..b6cf134b9dff --- /dev/null +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionMinMaxVectorProcessorBenchmark.java @@ -0,0 +1,348 @@ +/* + * 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.math.expr.Expr; +import org.apache.druid.math.expr.ExpressionType; +import org.apache.druid.math.expr.vector.DoubleBivariateDoubleLongFunctionVectorProcessor; +import org.apache.druid.math.expr.vector.DoubleBivariateDoublesFunctionVectorProcessor; +import org.apache.druid.math.expr.vector.DoubleBivariateLongDoubleFunctionVectorProcessor; +import org.apache.druid.math.expr.vector.ExprEvalDoubleVector; +import org.apache.druid.math.expr.vector.ExprEvalLongVector; +import org.apache.druid.math.expr.vector.ExprEvalVector; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.LongBivariateLongsFunctionVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction; +import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction; +import org.apache.druid.math.expr.vector.simd.SimdDoubleDoubleMaxProcessor; +import org.apache.druid.math.expr.vector.simd.SimdDoubleDoubleMinProcessor; +import org.apache.druid.math.expr.vector.simd.SimdDoubleLongMaxProcessor; +import org.apache.druid.math.expr.vector.simd.SimdDoubleLongMinProcessor; +import org.apache.druid.math.expr.vector.simd.SimdLongDoubleMaxProcessor; +import org.apache.druid.math.expr.vector.simd.SimdLongDoubleMinProcessor; +import org.apache.druid.math.expr.vector.simd.SimdLongLongMaxProcessor; +import org.apache.druid.math.expr.vector.simd.SimdLongLongMinProcessor; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +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.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 ExpressionMinMaxVectorProcessorBenchmark +{ + @Param({"128", "512", "1024", "4096"}) + private int vectorSize; + + @Param({"none", "sparse", "alternating"}) + private String nullPattern; + + @Param({"min", "max"}) + private String operation; + + @Param({"longLong", "doubleDouble", "longDouble", "doubleLong"}) + private String inputTypes; + + private Expr.VectorInputBinding bindings; + private ExprVectorProcessor vectorProcessor; + private ExprVectorProcessor simdProcessor; + + @Setup + public void setup() + { + final Random random = new Random(0xC0FFEEL); + final long[] leftLongs = new long[vectorSize]; + final long[] rightLongs = new long[vectorSize]; + final double[] leftDoubles = new double[vectorSize]; + final double[] rightDoubles = new double[vectorSize]; + for (int i = 0; i < vectorSize; i++) { + leftLongs[i] = random.nextLong(-1_000_000L, 1_000_000L); + rightLongs[i] = random.nextLong(-1_000_000L, 1_000_000L); + leftDoubles[i] = (random.nextDouble() - 0.5) * 1_000_000.0; + rightDoubles[i] = (random.nextDouble() - 0.5) * 1_000_000.0; + } + + final boolean[] leftNulls = makeNullVector(true); + final boolean[] rightNulls = makeNullVector(false); + bindings = new FakeVectorInputBinding(vectorSize); + + switch (inputTypes) { + case "longLong": + setupLongLong(leftLongs, rightLongs, leftNulls, rightNulls); + break; + case "doubleDouble": + setupDoubleDouble(leftDoubles, rightDoubles, leftNulls, rightNulls); + break; + case "longDouble": + setupLongDouble(leftLongs, rightDoubles, leftNulls, rightNulls); + break; + case "doubleLong": + setupDoubleLong(leftDoubles, rightLongs, leftNulls, rightNulls); + break; + default: + throw new IllegalStateException("Unsupported input types[" + inputTypes + "]"); + } + } + + @Benchmark + public void vectorMinMax(final Blackhole blackhole) + { + final ExprEvalVector result = vectorProcessor.evalVector(bindings); + blackhole.consume(result.values()); + blackhole.consume(result.getNullVector()); + } + + @Benchmark + public void simdMinMax(final Blackhole blackhole) + { + final ExprEvalVector result = simdProcessor.evalVector(bindings); + blackhole.consume(result.values()); + blackhole.consume(result.getNullVector()); + } + + private void setupLongLong( + final long[] left, + final long[] right, + @Nullable final boolean[] leftNulls, + @Nullable final boolean[] rightNulls + ) + { + final LongBivariateLongsFunction function = "max".equals(operation) ? Math::max : Math::min; + final ExprVectorProcessor leftProcessor = new FakeLongVectorProcessor(left, leftNulls); + final ExprVectorProcessor rightProcessor = new FakeLongVectorProcessor(right, rightNulls); + vectorProcessor = new LongBivariateLongsFunctionVectorProcessor(leftProcessor, rightProcessor, function); + simdProcessor = "max".equals(operation) + ? new SimdLongLongMaxProcessor(leftProcessor, rightProcessor, function) + : new SimdLongLongMinProcessor(leftProcessor, rightProcessor, function); + } + + private void setupDoubleDouble( + final double[] left, + final double[] right, + @Nullable final boolean[] leftNulls, + @Nullable final boolean[] rightNulls + ) + { + final DoubleBivariateDoublesFunction function = "max".equals(operation) ? Math::max : Math::min; + final ExprVectorProcessor leftProcessor = new FakeDoubleVectorProcessor(left, leftNulls); + final ExprVectorProcessor rightProcessor = new FakeDoubleVectorProcessor(right, rightNulls); + vectorProcessor = new DoubleBivariateDoublesFunctionVectorProcessor(leftProcessor, rightProcessor, function); + simdProcessor = "max".equals(operation) + ? new SimdDoubleDoubleMaxProcessor(leftProcessor, rightProcessor, function) + : new SimdDoubleDoubleMinProcessor(leftProcessor, rightProcessor, function); + } + + private void setupLongDouble( + final long[] left, + final double[] right, + @Nullable final boolean[] leftNulls, + @Nullable final boolean[] rightNulls + ) + { + final DoubleBivariateLongDoubleFunction function = "max".equals(operation) + ? (leftValue, rightValue) -> Math.max(leftValue, rightValue) + : (leftValue, rightValue) -> Math.min(leftValue, rightValue); + final ExprVectorProcessor leftProcessor = new FakeLongVectorProcessor(left, leftNulls); + final ExprVectorProcessor rightProcessor = new FakeDoubleVectorProcessor(right, rightNulls); + vectorProcessor = new DoubleBivariateLongDoubleFunctionVectorProcessor(leftProcessor, rightProcessor, function); + simdProcessor = "max".equals(operation) + ? new SimdLongDoubleMaxProcessor(leftProcessor, rightProcessor, function) + : new SimdLongDoubleMinProcessor(leftProcessor, rightProcessor, function); + } + + private void setupDoubleLong( + final double[] left, + final long[] right, + @Nullable final boolean[] leftNulls, + @Nullable final boolean[] rightNulls + ) + { + final DoubleBivariateDoubleLongFunction function = "max".equals(operation) + ? (leftValue, rightValue) -> Math.max(leftValue, rightValue) + : (leftValue, rightValue) -> Math.min(leftValue, rightValue); + final ExprVectorProcessor leftProcessor = new FakeDoubleVectorProcessor(left, leftNulls); + final ExprVectorProcessor rightProcessor = new FakeLongVectorProcessor(right, rightNulls); + vectorProcessor = new DoubleBivariateDoubleLongFunctionVectorProcessor(leftProcessor, rightProcessor, function); + simdProcessor = "max".equals(operation) + ? new SimdDoubleLongMaxProcessor(leftProcessor, rightProcessor, function) + : new SimdDoubleLongMinProcessor(leftProcessor, rightProcessor, function); + } + + @Nullable + private boolean[] makeNullVector(final boolean left) + { + return switch (nullPattern) { + case "none" -> null; + case "sparse" -> { + final boolean[] nulls = new boolean[vectorSize]; + for (int i = 0; i < vectorSize; i++) { + nulls[i] = left ? i % 17 == 0 : i % 19 == 0; + } + yield nulls; + } + case "alternating" -> { + final boolean[] nulls = new boolean[vectorSize]; + for (int i = 0; i < vectorSize; i++) { + nulls[i] = left ? (i & 1) == 0 : i % 5 == 0; + } + yield nulls; + } + default -> throw new IllegalStateException("Unsupported null pattern[" + nullPattern + "]"); + }; + } + + private static final class FakeLongVectorProcessor implements ExprVectorProcessor + { + private final ExprEvalLongVector eval; + private final int size; + + FakeLongVectorProcessor(final long[] values, @Nullable final boolean[] nulls) + { + this.eval = new ExprEvalLongVector(values, nulls); + this.size = values.length; + } + + @Override + public ExprEvalVector evalVector(final Expr.VectorInputBinding bindings) + { + return eval; + } + + @Override + public ExpressionType getOutputType() + { + return ExpressionType.LONG; + } + + @Override + public int maxVectorSize() + { + return size; + } + } + + private static final class FakeDoubleVectorProcessor implements ExprVectorProcessor + { + private final ExprEvalDoubleVector eval; + private final int size; + + FakeDoubleVectorProcessor(final double[] values, @Nullable final boolean[] nulls) + { + this.eval = new ExprEvalDoubleVector(values, nulls); + this.size = values.length; + } + + @Override + public ExprEvalVector evalVector(final Expr.VectorInputBinding bindings) + { + return eval; + } + + @Override + public ExpressionType getOutputType() + { + return ExpressionType.DOUBLE; + } + + @Override + public int maxVectorSize() + { + return size; + } + } + + private static final class FakeVectorInputBinding implements Expr.VectorInputBinding + { + private final int size; + + FakeVectorInputBinding(final int size) + { + this.size = size; + } + + @Nullable + @Override + public ExpressionType getType(final String name) + { + return null; + } + + @Override + public int getMaxVectorSize() + { + return size; + } + + @Override + public Object[] getObjectVector(final String name) + { + throw new UnsupportedOperationException(); + } + + @Override + public long[] getLongVector(final String name) + { + throw new UnsupportedOperationException(); + } + + @Override + public double[] getDoubleVector(final String name) + { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public boolean[] getNullVector(final String name) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getCurrentVectorSize() + { + return size; + } + + @Override + public int getCurrentVectorId() + { + return 0; + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java b/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java index 4a26f8141531..f1504d8b302b 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java @@ -446,7 +446,7 @@ public static class Max extends SimpleVectorMathBivariateProcessorFactory public Max() { - super(Math::max, Math::max, Math::max, Math::max); + super(Math::max, Math::max, Math::max, Math::max, SimdSupportedBinaryOp.MAX); } } @@ -456,7 +456,7 @@ public static class Min extends SimpleVectorMathBivariateProcessorFactory public Min() { - super(Math::min, Math::min, Math::min, Math::min); + super(Math::min, Math::min, Math::min, Math::min, SimdSupportedBinaryOp.MIN); } } diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMaxProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMaxProcessor.java new file mode 100644 index 000000000000..ed8b9aaac984 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMaxProcessor.java @@ -0,0 +1,93 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (double[], double[]) -> double[]} maximum. The op is hardcoded to + * {@link DoubleVector#max} so the JIT statically resolves it to the platform's double-max intrinsic. + */ +public final class SimdDoubleDoubleMaxProcessor extends SimdDoubleDoubleProcessor +{ + public SimdDoubleDoubleMaxProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + DoubleBivariateDoublesFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + double[] leftInput, + double[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = SPECIES.length(); + final int upperBound = SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i); + final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i); + va.max(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i) + .or(VectorMask.fromArray(SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(SPECIES, rightNulls, i); + } + final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i); + final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i); + va.max(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMinProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMinProcessor.java new file mode 100644 index 000000000000..db58c1b99557 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMinProcessor.java @@ -0,0 +1,93 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (double[], double[]) -> double[]} minimum. The op is hardcoded to + * {@link DoubleVector#min} so the JIT statically resolves it to the platform's double-min intrinsic. + */ +public final class SimdDoubleDoubleMinProcessor extends SimdDoubleDoubleProcessor +{ + public SimdDoubleDoubleMinProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + DoubleBivariateDoublesFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + double[] leftInput, + double[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = SPECIES.length(); + final int upperBound = SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i); + final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i); + va.min(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i) + .or(VectorMask.fromArray(SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(SPECIES, rightNulls, i); + } + final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i); + final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i); + va.min(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMaxProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMaxProcessor.java new file mode 100644 index 000000000000..a6c868372f69 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMaxProcessor.java @@ -0,0 +1,96 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (double[], long[]) -> double[]} maximum. The op is hardcoded to + * {@link DoubleVector#max} so the JIT statically resolves it to the platform's double-max intrinsic. + */ +public final class SimdDoubleLongMaxProcessor extends SimdDoubleLongProcessor +{ + public SimdDoubleLongMaxProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + DoubleBivariateDoubleLongFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + double[] leftInput, + long[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = DOUBLE_SPECIES.length(); + final int upperBound = DOUBLE_SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, leftInput, i); + final DoubleVector vb = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, i).castShape(DOUBLE_SPECIES, 0); + va.max(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i) + .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i); + } + final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, leftInput, i); + final DoubleVector vb = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, i).castShape(DOUBLE_SPECIES, 0); + va.max(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMinProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMinProcessor.java new file mode 100644 index 000000000000..91f738ddbe24 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMinProcessor.java @@ -0,0 +1,96 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (double[], long[]) -> double[]} minimum. The op is hardcoded to + * {@link DoubleVector#min} so the JIT statically resolves it to the platform's double-min intrinsic. + */ +public final class SimdDoubleLongMinProcessor extends SimdDoubleLongProcessor +{ + public SimdDoubleLongMinProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + DoubleBivariateDoubleLongFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + double[] leftInput, + long[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = DOUBLE_SPECIES.length(); + final int upperBound = DOUBLE_SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, leftInput, i); + final DoubleVector vb = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, i).castShape(DOUBLE_SPECIES, 0); + va.min(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i) + .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i); + } + final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, leftInput, i); + final DoubleVector vb = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, i).castShape(DOUBLE_SPECIES, 0); + va.min(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMaxProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMaxProcessor.java new file mode 100644 index 000000000000..718de0c06a27 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMaxProcessor.java @@ -0,0 +1,96 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (long[], double[]) -> double[]} maximum. The op is hardcoded to + * {@link DoubleVector#max} so the JIT statically resolves it to the platform's double-max intrinsic. + */ +public final class SimdLongDoubleMaxProcessor extends SimdLongDoubleProcessor +{ + public SimdLongDoubleMaxProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + DoubleBivariateLongDoubleFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + long[] leftInput, + double[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = DOUBLE_SPECIES.length(); + final int upperBound = DOUBLE_SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final DoubleVector va = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, i).castShape(DOUBLE_SPECIES, 0); + final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, rightInput, i); + va.max(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i) + .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i); + } + final DoubleVector va = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, i).castShape(DOUBLE_SPECIES, 0); + final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, rightInput, i); + va.max(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMinProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMinProcessor.java new file mode 100644 index 000000000000..51edfe7d63bf --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMinProcessor.java @@ -0,0 +1,96 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (long[], double[]) -> double[]} minimum. The op is hardcoded to + * {@link DoubleVector#min} so the JIT statically resolves it to the platform's double-min intrinsic. + */ +public final class SimdLongDoubleMinProcessor extends SimdLongDoubleProcessor +{ + public SimdLongDoubleMinProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + DoubleBivariateLongDoubleFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + long[] leftInput, + double[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = DOUBLE_SPECIES.length(); + final int upperBound = DOUBLE_SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final DoubleVector va = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, i).castShape(DOUBLE_SPECIES, 0); + final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, rightInput, i); + va.min(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i) + .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i); + } + final DoubleVector va = + (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, i).castShape(DOUBLE_SPECIES, 0); + final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, rightInput, i); + va.min(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMaxProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMaxProcessor.java new file mode 100644 index 000000000000..87f5e7ff57ab --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMaxProcessor.java @@ -0,0 +1,93 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (long[], long[]) -> long[]} maximum. The op is hardcoded to {@link LongVector#max} + * so the JIT statically resolves it to the platform's long-max intrinsic. + */ +public final class SimdLongLongMaxProcessor extends SimdLongLongProcessor +{ + public SimdLongLongMaxProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + LongBivariateLongsFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + long[] leftInput, + long[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = SPECIES.length(); + final int upperBound = SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final LongVector va = LongVector.fromArray(SPECIES, leftInput, i); + final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i); + va.max(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i) + .or(VectorMask.fromArray(SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(SPECIES, rightNulls, i); + } + final LongVector va = LongVector.fromArray(SPECIES, leftInput, i); + final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i); + va.max(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMinProcessor.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMinProcessor.java new file mode 100644 index 000000000000..0f29a43fec97 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMinProcessor.java @@ -0,0 +1,93 @@ +/* + * 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.math.expr.vector.simd; + +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import org.apache.druid.math.expr.vector.ExprVectorProcessor; +import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction; + +import java.util.Arrays; + +/** + * SIMD specialization of {@code (long[], long[]) -> long[]} minimum. The op is hardcoded to {@link LongVector#min} + * so the JIT statically resolves it to the platform's long-min intrinsic. + */ +public final class SimdLongLongMinProcessor extends SimdLongLongProcessor +{ + public SimdLongLongMinProcessor( + ExprVectorProcessor left, + ExprVectorProcessor right, + LongBivariateLongsFunction scalarFallback + ) + { + super(left, right, scalarFallback); + } + + @Override + protected void processVector( + long[] leftInput, + long[] rightInput, + boolean[] leftNulls, + boolean[] rightNulls, + int currentSize + ) + { + final boolean hasLeftNulls = leftNulls != null; + final boolean hasRightNulls = rightNulls != null; + final int laneCount = SPECIES.length(); + final int upperBound = SPECIES.loopBound(currentSize); + int i = 0; + if (!hasLeftNulls && !hasRightNulls) { + for (; i < upperBound; i += laneCount) { + final LongVector va = LongVector.fromArray(SPECIES, leftInput, i); + final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i); + va.min(vb).intoArray(outValues, i); + } + for (; i < currentSize; i++) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + Arrays.fill(outNulls, 0, currentSize, false); + } else { + for (; i < upperBound; i += laneCount) { + final VectorMask nm; + if (hasLeftNulls && hasRightNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i) + .or(VectorMask.fromArray(SPECIES, rightNulls, i)); + } else if (hasLeftNulls) { + nm = VectorMask.fromArray(SPECIES, leftNulls, i); + } else { + nm = VectorMask.fromArray(SPECIES, rightNulls, i); + } + final LongVector va = LongVector.fromArray(SPECIES, leftInput, i); + final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i); + va.min(vb).intoArray(outValues, i); + nm.intoArray(outNulls, i); + } + for (; i < currentSize; i++) { + final boolean isNull = (hasLeftNulls && leftNulls[i]) || (hasRightNulls && rightNulls[i]); + outNulls[i] = isNull; + if (!isNull) { + outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]); + } + } + } + } +} diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java index d8d74021c7a0..07f4b64446fc 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java @@ -47,6 +47,8 @@ public static ExprVectorProcessor makeLongLong( case ADD -> new SimdLongLongAddProcessor(left, right, scalarFallback); case SUB -> new SimdLongLongSubProcessor(left, right, scalarFallback); case MUL -> new SimdLongLongMulProcessor(left, right, scalarFallback); + case MIN -> new SimdLongLongMinProcessor(left, right, scalarFallback); + case MAX -> new SimdLongLongMaxProcessor(left, right, scalarFallback); default -> throw DruidException.defensive("Unsupported SIMD binary op[%s]", op); }; } @@ -62,6 +64,8 @@ public static ExprVectorProcessor makeDoubleDouble( case ADD -> new SimdDoubleDoubleAddProcessor(left, right, scalarFallback); case SUB -> new SimdDoubleDoubleSubProcessor(left, right, scalarFallback); case MUL -> new SimdDoubleDoubleMulProcessor(left, right, scalarFallback); + case MIN -> new SimdDoubleDoubleMinProcessor(left, right, scalarFallback); + case MAX -> new SimdDoubleDoubleMaxProcessor(left, right, scalarFallback); default -> throw DruidException.defensive("Unsupported SIMD binary op[%s]", op); }; } @@ -77,6 +81,8 @@ public static ExprVectorProcessor makeLongDouble( case ADD -> new SimdLongDoubleAddProcessor(left, right, scalarFallback); case SUB -> new SimdLongDoubleSubProcessor(left, right, scalarFallback); case MUL -> new SimdLongDoubleMulProcessor(left, right, scalarFallback); + case MIN -> new SimdLongDoubleMinProcessor(left, right, scalarFallback); + case MAX -> new SimdLongDoubleMaxProcessor(left, right, scalarFallback); default -> throw DruidException.defensive("Unsupported SIMD binary op[%s]", op); }; } @@ -92,6 +98,8 @@ public static ExprVectorProcessor makeDoubleLong( case ADD -> new SimdDoubleLongAddProcessor(left, right, scalarFallback); case SUB -> new SimdDoubleLongSubProcessor(left, right, scalarFallback); case MUL -> new SimdDoubleLongMulProcessor(left, right, scalarFallback); + case MIN -> new SimdDoubleLongMinProcessor(left, right, scalarFallback); + case MAX -> new SimdDoubleLongMaxProcessor(left, right, scalarFallback); default -> throw DruidException.defensive("Unsupported SIMD binary op[%s]", op); }; } diff --git a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java index 953571ba4d36..c98b16a9e532 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java +++ b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java @@ -32,5 +32,7 @@ public enum SimdSupportedBinaryOp { ADD, SUB, - MUL + MUL, + MIN, + MAX } diff --git a/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java b/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java index 9ffe5da3ace8..eb18e646c685 100644 --- a/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java +++ b/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java @@ -378,6 +378,56 @@ public void testBivariateMathFunctions() testFunctions(types, templates, functions); } + @Test + public void testBivariateMinMaxFloatingPointEdges() + { + final Map edgeTypes = + ImmutableMap.builder() + .put("d1", ExpressionType.DOUBLE) + .put("d2", ExpressionType.DOUBLE) + .put("l1", ExpressionType.LONG) + .put("l2", ExpressionType.LONG) + .build(); + final double[] d1 = {-0.0, 0.0, Double.NaN, 1.0, -2.5, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 7.0}; + final double[] d2 = {0.0, -0.0, 1.0, Double.NaN, -3.5, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 7.0}; + final long[] l1 = {0L, 0L, 1L, Long.MAX_VALUE, Long.MIN_VALUE, -3L, 5L, 7L}; + final long[] l2 = {1L, -1L, 1L, Long.MIN_VALUE, Long.MAX_VALUE, -4L, 4L, 7L}; + final int vectorSize = d1.length; + final boolean[] noNulls = new boolean[vectorSize]; + final SettableVectorInputBinding vectorBinding = + new SettableVectorInputBinding(vectorSize) + .addDouble("d1", d1, noNulls) + .addDouble("d2", d2, noNulls) + .addLong("l1", l1, noNulls) + .addLong("l2", l2, noNulls); + final SettableObjectBinding[] objectBindings = new SettableObjectBinding[vectorSize]; + final Expr.InputBindingInspector inspector = InputBindings.inspectorFromTypeMap(edgeTypes); + + for (int i = 0; i < vectorSize; i++) { + objectBindings[i] = new SettableObjectBinding() + .withInspector(inspector) + .withBinding("d1", d1[i]) + .withBinding("d2", d2[i]) + .withBinding("l1", l1[i]) + .withBinding("l2", l2[i]); + } + + final NonnullPair bindings = + new NonnullPair<>(objectBindings, vectorBinding); + for (final String expression : List.of( + "min(d1, d2)", + "max(d1, d2)", + "min(l1, d2)", + "max(l1, d2)", + "min(d1, l1)", + "max(d1, l1)", + "min(l1, l2)", + "max(l1, l2)" + )) { + assertEvalsMatch(expression, Parser.parse(expression, MACRO_TABLE), bindings); + } + } + @Test public void testSymmetricalBivariateFunctions() {