feat: simd double unary math ops - #19768
Conversation
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
Reviewed 36 of 36 changed files.
This is an automated review by Codex GPT-5.6-Sol
| final int upperBound = SPECIES.loopBound(currentSize); | ||
| int i = 0; | ||
| for (; i < upperBound; i += laneCount) { | ||
| DoubleVector.fromArray(SPECIES, input, i).lanewise(VectorOperators.SIN).intoArray(outValues, i); |
There was a problem hiding this comment.
[P1] Keep transcendental results stable across JIT warmup
VectorOperators.SIN initially matches Math.sin, but compiled SLEEF/SVML output can differ. On JDK 25/AArch64, this exact loop changed 665 of 8,192 lanes after warmup; most other added VO_MATHLIB operations also diverged. Compilation mid-query can therefore give identical inputs different bits, affecting exact filters or group keys. Keep these operations scalar unless stable semantics are established, and add warmed parity tests.
There was a problem hiding this comment.
hmm, fair. It does seem pretty unlikely that someone is going to be using sin(x) for grouping or equality filtering, so as a compromise I've split these functions out into a separate config useVectorMathApi (which also requires useVectorApi) and documented it to call out this behavior is possible. These configs are both off by default, and this at least makes them available to operators who know they are safe since I am unsure how to reliably pre-warm compilation like this reliably at this time. Added a test that shows that after-warm-up eventually stabilizes.
FrankChen021
left a comment
There was a problem hiding this comment.
Follow-up complete: the stability concern is addressed. VO_MATHLIB operations now require the separate, default-off useVectorMathApi gate in addition to useVectorApi, and the documentation explicitly warns against exact-equality use cases. The warmed parity test also captures the expected stabilization behavior.
Reviewed 41 of 41 changed files.
Validation: git diff --check passed.
This is an automated review by Codex GPT-5.6-Sol
FrankChen021
left a comment
There was a problem hiding this comment.
I have reviewed the updated code for correctness, edge cases, concurrency, and integration risks; no issues found.
Reviewed 42 of 42 changed files. The default-off vector-math gate remains intact; the latest update only adds required spelling exceptions.
This is an automated review by Codex GPT-5.6-Sol
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
Reviewed 42 of 42 changed files. The prior default-off stability finding is resolved; this updated review reports one new test-gating regression.
This is an automated review by Codex GPT-5.6-Luna(max)
| public static void initializeForVectorApiTests() | ||
| { | ||
| INSTANCE = new ExpressionProcessingConfig(null, null, null, true); | ||
| INSTANCE = new ExpressionProcessingConfig(null, null, null, true, true); |
There was a problem hiding this comment.
[P1] Keep exact vector-math tests from enabling approximate SIMD
initializeForVectorApiTests() enables approximate math SIMD, but the inherited exact-comparison suite uses Assert.assertEquals for functions such as sin and exp. This PR documents VO_MATHLIB ULP differences and tier-transition changes, so supported JDK/hardware combinations can now fail these tests. Keep this helper's math flag off, or make the consistency assertions tolerance-aware.
There was a problem hiding this comment.
This seems like it could be a legit issue with VectorExprResultConsistencyVectorApiTest, possibly dependent on the order in which things are run.
There was a problem hiding this comment.
The issue remains at current HEAD: initializeForVectorApiTests() still enables useVectorMathApi at line 60, while the inherited consistency suite compares SIMD and scalar math with exact equality. Please disable this flag in that helper or make the affected assertions tolerance-aware.
Reviewed 42 of 42 changed files.
| "true" | ||
| ); | ||
| this.useVectorApi = getWithPropertyFallbackFalse(useVectorApi, USE_VECTOR_API); | ||
| this.useVectorMathApi = getWithPropertyFallbackFalse(useVectorMathApi, USE_VECTOR_MATH_API); |
There was a problem hiding this comment.
IMO this should be true by default, since I believe most people that enable useVectorApi will want this. Setting it to false is a niche thing.
| * SIMD specialization of {@code (double[]) -> double[]} arc cosine. See {@link SimdSupportedUnaryOp#ACOS} for | ||
| * the shared VO_MATHLIB performance notes. | ||
| */ | ||
| public final class SimdDoubleAcosProcessor extends SimdDoubleUnaryProcessor |
There was a problem hiding this comment.
There are so many of these. I wonder if generating the classes at runtime would be better than having all the sources here. Could be something to explore in a follow-up.
| } | ||
| // VO_MATHLIB ops (SVML/SLEEF-backed transcendentals) sit behind the additional useVectorMathApi opt-in | ||
| // because they can produce different bits than Math.<op> once JIT tier transitions the loop from C1 to C2. | ||
| return !simdOp.isMathLib() || ExpressionProcessing.useVectorMathApi(); |
There was a problem hiding this comment.
Should probably have a version of this check in SimdSupportedBinaryOp and SimpleVectorMathBivariateProcessorFactory too. Maybe nothing would return true today, but it seems useful to have it there so it's not forgotten in the future when more ops are added.
Or, possibly even better, move this all into simdOp.isSimdEnabled().
| * {@link #useVectorApi()}. | ||
| * | ||
| * <p>These ops route through Intel SVML / Arm SLEEF once the JIT compiles the vector loop to C2; before that | ||
| * compilation, they fall back to per-lane {@link Math} calls. The two paths can differ by up to 1 ulp, so a |
There was a problem hiding this comment.
This says up to 1 ulp, but configuration/index.md says "a few ulps" and the test SimdVoMathlibParityTest.MAX_ULPS is checking for 4 ulps. Please make all the commentary about accuracy consistent.
| } | ||
|
|
||
| /** | ||
| * Whether SIMD dispatch is allowed for math ops backed by the JDK's VO_MATHLIB path (LOG, EXP, SIN, etc). Off by |
There was a problem hiding this comment.
IMO should be on by default (see other comment).
| |`druid.expressions.homogenizeNullMultiValueStringArrays`|If true, multi-value string expression input values of `null`, `[]`, and `[null]` are all coerced to `[null]`. Provided for backwards compatibility with Druid 0.22 and earlier. If false (the default), this coercion only happens when single-value expressions are implicitly mapped across multi-value rows, so the single-valued expression is evaluated with an input of `null`.|false| | ||
| |`druid.expressions.allowVectorizeFallback`|If true, the vectorized query engine handles expressions without a native vectorized implementation using a fallback processor that invokes the scalar expression evaluator in a loop. If false, such expressions cannot be vectorized and the query falls back to the non-vectorized engine.|true| | ||
| |`druid.expressions.useVectorApi`|If true, vectorized expression vector processors and numeric vector aggregators dispatch to SIMD specializations backed by the JDK incubator Vector API (`jdk.incubator.vector`) where available. Requires `--add-modules=jdk.incubator.vector` on the JVM command line (see [strong encapsulation](../operations/java.md#strong-encapsulation)). Off by default while the Vector API remains an incubator JDK feature.|false| | ||
| |`druid.expressions.useVectorMathApi`|If true (and `useVectorApi` is also true), math expressions whose SIMD path is backed by the JDK's VO_MATHLIB routing (Intel SVML on x86, SLEEF on Arm) dispatch to that SIMD path. Results can differ from the scalar `Math.<op>` by up to a few ulps, and the exact bits can shift once the JIT promotes the SIMD loop from C1 to C2, meaning a long-running query can produce different bits for the same input across the tier transition. Safe for `SUM`/`MIN`/`MAX`/range-filter workloads; **do not enable** if queries rely on exact-bit equality of these functions (for example `GROUP BY sin(x)` where all rows with the same `x` must land in the same group, or `WHERE sin(x) = sin(y)`). Off by default.|false| |
There was a problem hiding this comment.
We definitely shouldn't encourage users to rely on exact-bit equality for floating point numbers. It's better to suggest that people truncate or round for comparison.
| public static void initializeForVectorApiTests() | ||
| { | ||
| INSTANCE = new ExpressionProcessingConfig(null, null, null, true); | ||
| INSTANCE = new ExpressionProcessingConfig(null, null, null, true, true); |
There was a problem hiding this comment.
This seems like it could be a legit issue with VectorExprResultConsistencyVectorApiTest, possibly dependent on the order in which things are run.
| DoubleVector.fromArray(SPECIES, input, i).lanewise(VectorOperators.ACOS).intoArray(outValues, i); | ||
| } | ||
| for (; i < currentSize; i++) { | ||
| outValues[i] = scalarFallback.process(input[i]); |
There was a problem hiding this comment.
This approach of doing scalar fallbacks means that the accuracy discrepancies are more pervasive than the docs in configuration/index.md suggest. The final vector in a cursor (which is likely to be shorter than the max size) will be using different math ops than the other vectors. Probably the best way to fix it is to use a vector op here too, with a mask.
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 0 |
| P2 | 1 |
| P3 | 0 |
| Total | 1 |
Reviewed 46 of 46 changed files. The prior test-gating concern appears resolved; this updated review found one test-helper edge case.
Validation: focused git diff --no-ext-diff --check passed. Builds and tests were not run.
This is an automated review by Codex GPT-5.6-Luna(max)
| } | ||
| final double ulp = Math.ulp(expected); | ||
| final double diff = Math.abs(actual - expected); | ||
| if (Double.isNaN(diff) || diff > (double) maxUlps * ulp) { |
There was a problem hiding this comment.
P2 Handle non-finite expected values before ULP comparison
When expected is infinite and actual is finite or has the opposite sign, Math.ulp(expected) and the comparison threshold are infinite, so this assertion can incorrectly pass. Handle non-finite values with exact equality before calculating the ULP threshold so parity tests fail when an infinite expected result is not reproduced.
Description
Adds a bunch of simd optimizations of the remaining double unary operators. The operators do not have direct intrinsics but are backed by a vectorized math library that depends on platform and jdk build, so performance may vary.
Along the way I found sinh, cosh, and tanh were not wired up to SQL, so have added those
Testing with a M1 mac I generally saw a pretty nice improvement: