Skip to content

Commit 111b2a6

Browse files
committed
perf: add SIMD acceleration for min/max long/double expressions
1 parent e44c216 commit 111b2a6

13 files changed

Lines changed: 1167 additions & 3 deletions
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.druid.benchmark;
21+
22+
import org.apache.druid.math.expr.Expr;
23+
import org.apache.druid.math.expr.ExpressionType;
24+
import org.apache.druid.math.expr.vector.DoubleBivariateDoubleLongFunctionVectorProcessor;
25+
import org.apache.druid.math.expr.vector.DoubleBivariateDoublesFunctionVectorProcessor;
26+
import org.apache.druid.math.expr.vector.DoubleBivariateLongDoubleFunctionVectorProcessor;
27+
import org.apache.druid.math.expr.vector.ExprEvalDoubleVector;
28+
import org.apache.druid.math.expr.vector.ExprEvalLongVector;
29+
import org.apache.druid.math.expr.vector.ExprEvalVector;
30+
import org.apache.druid.math.expr.vector.ExprVectorProcessor;
31+
import org.apache.druid.math.expr.vector.LongBivariateLongsFunctionVectorProcessor;
32+
import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
33+
import org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
34+
import org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
35+
import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
36+
import org.apache.druid.math.expr.vector.simd.SimdDoubleDoubleMaxProcessor;
37+
import org.apache.druid.math.expr.vector.simd.SimdDoubleDoubleMinProcessor;
38+
import org.apache.druid.math.expr.vector.simd.SimdDoubleLongMaxProcessor;
39+
import org.apache.druid.math.expr.vector.simd.SimdDoubleLongMinProcessor;
40+
import org.apache.druid.math.expr.vector.simd.SimdLongDoubleMaxProcessor;
41+
import org.apache.druid.math.expr.vector.simd.SimdLongDoubleMinProcessor;
42+
import org.apache.druid.math.expr.vector.simd.SimdLongLongMaxProcessor;
43+
import org.apache.druid.math.expr.vector.simd.SimdLongLongMinProcessor;
44+
import org.openjdk.jmh.annotations.Benchmark;
45+
import org.openjdk.jmh.annotations.BenchmarkMode;
46+
import org.openjdk.jmh.annotations.Fork;
47+
import org.openjdk.jmh.annotations.Measurement;
48+
import org.openjdk.jmh.annotations.Mode;
49+
import org.openjdk.jmh.annotations.OutputTimeUnit;
50+
import org.openjdk.jmh.annotations.Param;
51+
import org.openjdk.jmh.annotations.Scope;
52+
import org.openjdk.jmh.annotations.Setup;
53+
import org.openjdk.jmh.annotations.State;
54+
import org.openjdk.jmh.annotations.Warmup;
55+
import org.openjdk.jmh.infra.Blackhole;
56+
57+
import javax.annotation.Nullable;
58+
import java.util.Random;
59+
import java.util.concurrent.TimeUnit;
60+
61+
@State(Scope.Benchmark)
62+
@Fork(value = 1, jvmArgsAppend = "--add-modules=jdk.incubator.vector")
63+
@Warmup(iterations = 5)
64+
@Measurement(iterations = 5)
65+
@BenchmarkMode(Mode.AverageTime)
66+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
67+
public class ExpressionMinMaxVectorProcessorBenchmark
68+
{
69+
@Param({"128", "512", "1024", "4096"})
70+
private int vectorSize;
71+
72+
@Param({"none", "sparse", "alternating"})
73+
private String nullPattern;
74+
75+
@Param({"min", "max"})
76+
private String operation;
77+
78+
@Param({"longLong", "doubleDouble", "longDouble", "doubleLong"})
79+
private String inputTypes;
80+
81+
private Expr.VectorInputBinding bindings;
82+
private ExprVectorProcessor<?> scalarProcessor;
83+
private ExprVectorProcessor<?> simdProcessor;
84+
85+
@Setup
86+
public void setup()
87+
{
88+
final Random random = new Random(0xC0FFEEL);
89+
final long[] leftLongs = new long[vectorSize];
90+
final long[] rightLongs = new long[vectorSize];
91+
final double[] leftDoubles = new double[vectorSize];
92+
final double[] rightDoubles = new double[vectorSize];
93+
for (int i = 0; i < vectorSize; i++) {
94+
leftLongs[i] = random.nextLong(-1_000_000L, 1_000_000L);
95+
rightLongs[i] = random.nextLong(-1_000_000L, 1_000_000L);
96+
leftDoubles[i] = (random.nextDouble() - 0.5) * 1_000_000.0;
97+
rightDoubles[i] = (random.nextDouble() - 0.5) * 1_000_000.0;
98+
}
99+
100+
final boolean[] leftNulls = makeNullVector(true);
101+
final boolean[] rightNulls = makeNullVector(false);
102+
bindings = new FakeVectorInputBinding(vectorSize);
103+
104+
switch (inputTypes) {
105+
case "longLong":
106+
setupLongLong(leftLongs, rightLongs, leftNulls, rightNulls);
107+
break;
108+
case "doubleDouble":
109+
setupDoubleDouble(leftDoubles, rightDoubles, leftNulls, rightNulls);
110+
break;
111+
case "longDouble":
112+
setupLongDouble(leftLongs, rightDoubles, leftNulls, rightNulls);
113+
break;
114+
case "doubleLong":
115+
setupDoubleLong(leftDoubles, rightLongs, leftNulls, rightNulls);
116+
break;
117+
default:
118+
throw new IllegalStateException("Unsupported input types[" + inputTypes + "]");
119+
}
120+
}
121+
122+
@Benchmark
123+
public void scalarMinMax(final Blackhole blackhole)
124+
{
125+
final ExprEvalVector<?> result = scalarProcessor.evalVector(bindings);
126+
blackhole.consume(result.values());
127+
blackhole.consume(result.getNullVector());
128+
}
129+
130+
@Benchmark
131+
public void simdMinMax(final Blackhole blackhole)
132+
{
133+
final ExprEvalVector<?> result = simdProcessor.evalVector(bindings);
134+
blackhole.consume(result.values());
135+
blackhole.consume(result.getNullVector());
136+
}
137+
138+
private void setupLongLong(
139+
final long[] left,
140+
final long[] right,
141+
@Nullable final boolean[] leftNulls,
142+
@Nullable final boolean[] rightNulls
143+
)
144+
{
145+
final LongBivariateLongsFunction function = "max".equals(operation) ? Math::max : Math::min;
146+
final ExprVectorProcessor<long[]> leftProcessor = new FakeLongVectorProcessor(left, leftNulls);
147+
final ExprVectorProcessor<long[]> rightProcessor = new FakeLongVectorProcessor(right, rightNulls);
148+
scalarProcessor = new LongBivariateLongsFunctionVectorProcessor(leftProcessor, rightProcessor, function);
149+
simdProcessor = "max".equals(operation)
150+
? new SimdLongLongMaxProcessor(leftProcessor, rightProcessor, function)
151+
: new SimdLongLongMinProcessor(leftProcessor, rightProcessor, function);
152+
}
153+
154+
private void setupDoubleDouble(
155+
final double[] left,
156+
final double[] right,
157+
@Nullable final boolean[] leftNulls,
158+
@Nullable final boolean[] rightNulls
159+
)
160+
{
161+
final DoubleBivariateDoublesFunction function = "max".equals(operation) ? Math::max : Math::min;
162+
final ExprVectorProcessor<double[]> leftProcessor = new FakeDoubleVectorProcessor(left, leftNulls);
163+
final ExprVectorProcessor<double[]> rightProcessor = new FakeDoubleVectorProcessor(right, rightNulls);
164+
scalarProcessor = new DoubleBivariateDoublesFunctionVectorProcessor(leftProcessor, rightProcessor, function);
165+
simdProcessor = "max".equals(operation)
166+
? new SimdDoubleDoubleMaxProcessor(leftProcessor, rightProcessor, function)
167+
: new SimdDoubleDoubleMinProcessor(leftProcessor, rightProcessor, function);
168+
}
169+
170+
private void setupLongDouble(
171+
final long[] left,
172+
final double[] right,
173+
@Nullable final boolean[] leftNulls,
174+
@Nullable final boolean[] rightNulls
175+
)
176+
{
177+
final DoubleBivariateLongDoubleFunction function = "max".equals(operation)
178+
? (leftValue, rightValue) -> Math.max(leftValue, rightValue)
179+
: (leftValue, rightValue) -> Math.min(leftValue, rightValue);
180+
final ExprVectorProcessor<long[]> leftProcessor = new FakeLongVectorProcessor(left, leftNulls);
181+
final ExprVectorProcessor<double[]> rightProcessor = new FakeDoubleVectorProcessor(right, rightNulls);
182+
scalarProcessor = new DoubleBivariateLongDoubleFunctionVectorProcessor(leftProcessor, rightProcessor, function);
183+
simdProcessor = "max".equals(operation)
184+
? new SimdLongDoubleMaxProcessor(leftProcessor, rightProcessor, function)
185+
: new SimdLongDoubleMinProcessor(leftProcessor, rightProcessor, function);
186+
}
187+
188+
private void setupDoubleLong(
189+
final double[] left,
190+
final long[] right,
191+
@Nullable final boolean[] leftNulls,
192+
@Nullable final boolean[] rightNulls
193+
)
194+
{
195+
final DoubleBivariateDoubleLongFunction function = "max".equals(operation)
196+
? (leftValue, rightValue) -> Math.max(leftValue, rightValue)
197+
: (leftValue, rightValue) -> Math.min(leftValue, rightValue);
198+
final ExprVectorProcessor<double[]> leftProcessor = new FakeDoubleVectorProcessor(left, leftNulls);
199+
final ExprVectorProcessor<long[]> rightProcessor = new FakeLongVectorProcessor(right, rightNulls);
200+
scalarProcessor = new DoubleBivariateDoubleLongFunctionVectorProcessor(leftProcessor, rightProcessor, function);
201+
simdProcessor = "max".equals(operation)
202+
? new SimdDoubleLongMaxProcessor(leftProcessor, rightProcessor, function)
203+
: new SimdDoubleLongMinProcessor(leftProcessor, rightProcessor, function);
204+
}
205+
206+
@Nullable
207+
private boolean[] makeNullVector(final boolean left)
208+
{
209+
return switch (nullPattern) {
210+
case "none" -> null;
211+
case "sparse" -> {
212+
final boolean[] nulls = new boolean[vectorSize];
213+
for (int i = 0; i < vectorSize; i++) {
214+
nulls[i] = left ? i % 17 == 0 : i % 19 == 0;
215+
}
216+
yield nulls;
217+
}
218+
case "alternating" -> {
219+
final boolean[] nulls = new boolean[vectorSize];
220+
for (int i = 0; i < vectorSize; i++) {
221+
nulls[i] = left ? (i & 1) == 0 : i % 5 == 0;
222+
}
223+
yield nulls;
224+
}
225+
default -> throw new IllegalStateException("Unsupported null pattern[" + nullPattern + "]");
226+
};
227+
}
228+
229+
private static final class FakeLongVectorProcessor implements ExprVectorProcessor<long[]>
230+
{
231+
private final ExprEvalLongVector eval;
232+
private final int size;
233+
234+
FakeLongVectorProcessor(final long[] values, @Nullable final boolean[] nulls)
235+
{
236+
this.eval = new ExprEvalLongVector(values, nulls);
237+
this.size = values.length;
238+
}
239+
240+
@Override
241+
public ExprEvalVector<long[]> evalVector(final Expr.VectorInputBinding bindings)
242+
{
243+
return eval;
244+
}
245+
246+
@Override
247+
public ExpressionType getOutputType()
248+
{
249+
return ExpressionType.LONG;
250+
}
251+
252+
@Override
253+
public int maxVectorSize()
254+
{
255+
return size;
256+
}
257+
}
258+
259+
private static final class FakeDoubleVectorProcessor implements ExprVectorProcessor<double[]>
260+
{
261+
private final ExprEvalDoubleVector eval;
262+
private final int size;
263+
264+
FakeDoubleVectorProcessor(final double[] values, @Nullable final boolean[] nulls)
265+
{
266+
this.eval = new ExprEvalDoubleVector(values, nulls);
267+
this.size = values.length;
268+
}
269+
270+
@Override
271+
public ExprEvalVector<double[]> evalVector(final Expr.VectorInputBinding bindings)
272+
{
273+
return eval;
274+
}
275+
276+
@Override
277+
public ExpressionType getOutputType()
278+
{
279+
return ExpressionType.DOUBLE;
280+
}
281+
282+
@Override
283+
public int maxVectorSize()
284+
{
285+
return size;
286+
}
287+
}
288+
289+
private static final class FakeVectorInputBinding implements Expr.VectorInputBinding
290+
{
291+
private final int size;
292+
293+
FakeVectorInputBinding(final int size)
294+
{
295+
this.size = size;
296+
}
297+
298+
@Nullable
299+
@Override
300+
public ExpressionType getType(final String name)
301+
{
302+
return null;
303+
}
304+
305+
@Override
306+
public int getMaxVectorSize()
307+
{
308+
return size;
309+
}
310+
311+
@Override
312+
public Object[] getObjectVector(final String name)
313+
{
314+
throw new UnsupportedOperationException();
315+
}
316+
317+
@Override
318+
public long[] getLongVector(final String name)
319+
{
320+
throw new UnsupportedOperationException();
321+
}
322+
323+
@Override
324+
public double[] getDoubleVector(final String name)
325+
{
326+
throw new UnsupportedOperationException();
327+
}
328+
329+
@Nullable
330+
@Override
331+
public boolean[] getNullVector(final String name)
332+
{
333+
throw new UnsupportedOperationException();
334+
}
335+
336+
@Override
337+
public int getCurrentVectorSize()
338+
{
339+
return size;
340+
}
341+
342+
@Override
343+
public int getCurrentVectorId()
344+
{
345+
return 0;
346+
}
347+
}
348+
}

processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ public static class Max extends SimpleVectorMathBivariateProcessorFactory
446446

447447
public Max()
448448
{
449-
super(Math::max, Math::max, Math::max, Math::max);
449+
super(Math::max, Math::max, Math::max, Math::max, SimdSupportedBinaryOp.MAX);
450450
}
451451
}
452452

@@ -456,7 +456,7 @@ public static class Min extends SimpleVectorMathBivariateProcessorFactory
456456

457457
public Min()
458458
{
459-
super(Math::min, Math::min, Math::min, Math::min);
459+
super(Math::min, Math::min, Math::min, Math::min, SimdSupportedBinaryOp.MIN);
460460
}
461461
}
462462

0 commit comments

Comments
 (0)