GH-47287: [C++][Compute] Add constraint for kernel signature matching and use it for binary decimal arithmetic kernels#47297
Conversation
…cimal arithmetic kernels
|
|
|
@github-actions crossbow submit -g cpp |
|
Looping in some people who might be interested. @pitrou @kou @westonpace @bkietz @felipecrv @ZhangHuiGui |
|
Revision: 4897960 Submitted crossbow builds: ursacomputing/crossbow @ actions-fc605b322e |
There was a problem hiding this comment.
Pull Request Overview
This PR introduces a new MatchConstraint mechanism for applying additional validation constraints to kernel signature matching beyond basic input type checks. The primary purpose is to add more refined matching logic for binary decimal arithmetic operations.
Key changes include:
- Introduction of
MatchConstraintabstract base class and two concrete implementations for decimal operations - Integration of constraints into
KernelSignatureandScalarFunction::AddKernel - Application of constraints to binary decimal arithmetic kernels (add/subtract require same scale, divide requires first scale >= second scale)
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cpp/src/arrow/compute/kernel.h | Defines MatchConstraint interface and declares constraint factory functions |
| cpp/src/arrow/compute/kernel.cc | Implements MatchConstraint concrete classes and integrates constraint validation into signature matching |
| cpp/src/arrow/compute/function.h | Updates ScalarFunction::AddKernel to accept optional constraint parameter |
| cpp/src/arrow/compute/function.cc | Passes constraint to KernelSignature::Make in AddKernel implementation |
| cpp/src/arrow/compute/kernels/scalar_arithmetic.cc | Applies specific constraints to decimal binary arithmetic kernels |
| cpp/src/arrow/compute/kernel_test.cc | Adds comprehensive tests for the new constraint functionality |
| return true; | ||
| } | ||
| }; | ||
| static auto instance = std::make_shared<DecimalsHaveSameScaleConstraint>(); |
There was a problem hiding this comment.
The static instance may not be thread-safe during initialization. Consider using std::call_once or similar synchronization mechanism to ensure thread-safe initialization.
| static auto instance = std::make_shared<DecimalsHaveSameScaleConstraint>(); | |
| static std::once_flag once; | |
| static std::shared_ptr<DecimalsHaveSameScaleConstraint> instance; | |
| std::call_once(once, []() { | |
| instance = std::make_shared<DecimalsHaveSameScaleConstraint>(); | |
| }); |
There was a problem hiding this comment.
Hmm, did someone decide to add Copilot reviews to the Arrow repo, or is that up to each PR author?
There was a problem hiding this comment.
IIRC it was in the "suggested reviewer" list, and I clicked "request" then.
There was a problem hiding this comment.
It's not a apache/arrow configuration.
PR authors and apache/arrow committers can set Copilot as a reviewer manually.
To use Copilot, they need to enable Copilot explicitly for their account: https://docs.github.com/en/copilot/get-started/quickstart#sign-up-for-github-copilot
|
also cc @lidavidm |
|
Can we merge this now? |
I think we can keep it open for another two or three days. And thanks for reviewing! |
|
I can take a look on Monday. |
310471f to
75c8761
Compare
cpp/src/arrow/compute/kernel.h
Outdated
|
|
||
| /// \brief Constraint that all binary input types are decimal types and the first type's | ||
| /// scale >= the second type's. | ||
| ARROW_EXPORT std::shared_ptr<MatchConstraint> BinaryDecimalScaleComparisonGE(); |
There was a problem hiding this comment.
The name is unfortunately a bit cryptic. Also, it's not obvious to me why we would require the first decimal to have a larger scale...
There was a problem hiding this comment.
Is BinaryDecimalScale1GeScale2 better?
Also, it's not obvious to me why we would require the first decimal to have a larger scale...
It's required by divide(decimal, decimal) kernels:
arrow/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
Lines 624 to 639 in 69d2487
There was a problem hiding this comment.
Is
BinaryDecimalScale1GeScale2better?
Yes :) Not pretty, but more explicit anyway.
There was a problem hiding this comment.
Thanks. Updated.
| out_type = OutputType(ResolveDecimalMultiplicationOutput); | ||
| } else if (op == "divide") { | ||
| out_type = OutputType(ResolveDecimalDivisionOutput); | ||
| constraint = BinaryDecimalScaleComparisonGE(); |
There was a problem hiding this comment.
Since we add some constraints here, does it mean that we can remove some error return paths from the various ResolveDecimal functions?
There was a problem hiding this comment.
Good point. Yes that's possible, as we are now suppressing the match for decimal arguments not qualifying the constraints. The explicit error returning could be a simple DCHECK now.
| DCHECK_OK(func->AddKernel({in_type128, in_type128}, out_type, exec128, /*init=*/nullptr, | ||
| constraint)); | ||
| DCHECK_OK(func->AddKernel({in_type256, in_type256}, out_type, exec256, /*init=*/nullptr, | ||
| constraint)); |
There was a problem hiding this comment.
Side note: do we have an issue open to extend this to Decimal32 and Decimal64, or are these kernels already registered elsewhere?
pitrou
left a comment
There was a problem hiding this comment.
Looks good on the principle, I posted various minor comments with perhaps opportunities for improvement.
|
Thanks for looking @pitrou . I'll update soon. |
|
Hi @pitrou , can we merge it now? |
|
I'll later follow up on validating the related issues listed in the PR description. Hopefully we can close or fix most of them. |
|
After merging your PR, Conbench analyzed the 4 benchmarking runs that have been run so far on merge-commit dadc21f. There weren't enough matching historic benchmark results to make a call on whether there were regressions. The full Conbench report has more details. |
…47445) ### Rationale for this change The issues in #40911 and #39875 are the same: we have a fundamental defect when dispatching kernels for decimal division. The following statements assume both the dividend and the divisor are of the same decimal type (`Decimal32/64/128/256`), with possibly different `(p, s)`. * When doing `DispatchBest`, which is directly invoked through `CallFunction("divide", ...)`, w/o trying `DispatchExact` ahead, the dividend is ALWAYS promoted and the result will have the same `(p, s)` as the dividend, according to the rule listed in our documentation [1] (this is actually adopting the Redshift one [2]). * When doing `DispatchExact`, which is first tried by expression evaluation, there will be a match w/o any promotions so the subsequent try of `DispatchMatch` won't happen. The issue is obvious - `DispatchExact` and `DispatchBest` are conflicting - one saying "OK, for any `decimal128(p1, s1) / decimal128(p2, s2)`, it is a match" and the other saying "No, we must promote the dividend according to `(p1, s1)` and `(p2, s2)`". Then we actually have two choices to fix it: 1. Consider `DispatchBest` is doing the right thing (justified by [1]), and NEVER "exact match" any kernel for decimal division. This is what this PR does. The only problem is that we are basically ALWAYS rejecting a kernel to be "exactly matched" - weird, though functionally correct. 2. Consider `DispatchExact` is doing the right thing, and NOT promoting dividend in `DispatchBest`. The kernel is matched only based on their decimal type (not considering their `(p, s)`). And only the result is promoted (this also complies [1]). This is what the other attempting PR #40969 does. But that PR only claims a promoted result type w/o actually promoting the computation (i.e., the memory representation of a decimal needs to be promoted when doing the division) so the result is wrong. Though this is amendable by supporting basic decimal methods like `PromoteAndDivide` that does the promotion of the dividend and the division all together in one run, the modification can be cumbersome - the "scale up" needs to be propagated from the kernel definition all down to the basic decimal primitives. Besides, I assume this may not be as performant as doing batch promotion + batch division. [1] https://docs.aws.amazon.com/redshift/latest/dg/r_numeric_computations201.html#r_numeric_computations201-precision-and-scale-of-computed-decimal-results [2] https://arrow.apache.org/docs/cpp/compute.html#arithmetic-functions ### What changes are included in this PR? Suppress the `DispatchExact` for decimal division. Also, the match constraint `BinaryDecimalScale1GeScale2` introduced in #47297 becomes useless thus gets removed. ### Are these changes tested? Yes. ### Are there any user-facing changes? None. * GitHub Issue: #40911 Authored-by: Rossi Sun <zanmato1984@gmail.com> Signed-off-by: Antoine Pitrou <antoine@python.org>
…ith different precisions and scales (#47479) ### Rationale for this change Another case of decimal kernels not able to suppress exact matching when precisions and scales of the arguments differ, causing wrong result type. After #47297, we have a systematic way to do that and guide the matching to go to the "best match" (applying implicit casts). ### What changes are included in this PR? Simply added a constraint match that checks if the precisions and scales of the decimal arguments are the same. Also added corresponding tests in forms of both expression (exact match first, then best match) and function call (best match only). ### Are these changes tested? Yes. ### Are there any user-facing changes? None. * GitHub Issue: #41336 Authored-by: Rossi Sun <zanmato1984@gmail.com> Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
… not handle decimal arguments with different scales (#47459) ### Rationale for this change We used to be not able to suppress the exact matching for decimal arguments with different scales, when a decimal comparison kernel who actually requires the scales to be the same. This caused issue like #41011. The "match constraint" introduced in #47297 is exactly for fixing issues like this, by simply adding a proper constraint. ### What changes are included in this PR? Added match constraint that requires all decimal inputs have the same scale (like for decimal addition and subtract). ### Are these changes tested? Yes. ### Are there any user-facing changes? None. * GitHub Issue: #41011 Lead-authored-by: Rossi Sun <zanmato1984@gmail.com> Co-authored-by: Antoine Pitrou <pitrou@free.fr> Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
Rationale for this change
A rework of #40223 using a more systematic alternative.
What changes are included in this PR?
Introduce a structure
MatchConstraintfor applying extra (and optional) matching constraint for kernel signature matching, in additional to simply input type checks.Also implement two concrete
MatchConstraints for binary decimal arithmetic kernels, to suppress exact match even if the input types are OK, for example, by requiring all decimal must be of the same scale foraddandsubtract, and s1 >= s2 fordivide.This should also be a fundamental enhancement to further resolve similar issues like:
(Haven't try each one of them. May do that if this PR gets merged.)
Are these changes tested?
UT included.
Are there any user-facing changes?
New public class
MatchConstraint.