[opt](decimal) fast paths for wide-integer (Decimal256) division - #66391
Open
Baymine wants to merge 1 commit into
Open
[opt](decimal) fast paths for wide-integer (Decimal256) division#66391Baymine wants to merge 1 commit into
Baymine wants to merge 1 commit into
Conversation
### What problem does this PR solve?
Issue Number: no issue
Problem Summary:
`wide::integer` division (backing Decimal256 and other >128-bit integer types)
always fell back to a generic bit-by-bit binary long-division loop that iterates
~Bits times (256 shift/compare/subtract rounds for a 256-bit value), even when
the operands are small. Decimal256 arithmetic and the CEIL/ROUND `x / 10^k`
rounding paths hit this hot loop constantly with operands that are far narrower
than 256 bits, so the general algorithm dominates the cost.
This adds three stacked fast paths in front of the generic loop, each bit-exact
with it (verified against native `__int128` oracles and via q*d+r==n identities),
and each writing the remainder back into `numerator` so `operator%` stays correct:
1. Both operands fit in 128 bits (the common money/count magnitude): perform a
single native `unsigned __int128` divide. Placed first because it is the
cheapest and most frequently hit.
2. Divisor fits in a single 64-bit limb (e.g. `x / 10^k`): schoolbook word-by-word
long division, one hardware 128/64 divide per limb -- O(item_count) divides
instead of ~Bits iterations.
3. Divisor fits in two 64-bit limbs (65..128-bit divisor): route through a new
`divide_knuth()` helper implementing Knuth's Algorithm D (Hacker's Delight
`divmnu`) in base 2^32, which keeps every intermediate product within a
uint64_t and stays overflow-safe.
Divisors wider than 128 bits and the zero-divisor throw are unchanged and fall
through to the existing generic path. On a fast-path miss the only added cost is
a short limb scan.
### Release note
None
### Check List (For Author)
- Test: Unit Test
- Added/extended `be/test/core/wide_integer_test.cpp` (18 new cases:
single-limb, two-limb Knuth, both-fit-128, signed, boundary, divide-by-zero,
and randomized differential/ground-truth fuzz against native __int128).
All 23 WideInteger tests pass locally (ASAN build).
- Behavior changed: No (pure performance optimization; results are bit-exact with
the previous slow path)
- Does this need documentation: No
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
Contributor
Author
|
run buildall |
Contributor
TPC-H: Total hot run time: 28797 ms |
Contributor
TPC-DS: Total hot run time: 168551 ms |
Contributor
ClickBench: Total hot run time: 23.74 s |
Contributor
BE UT Coverage ReportIncrement line coverage Increment coverage report
|
Contributor
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What problem does this PR solve?
Issue Number: no issue
Problem Summary:
wide::integerdivision (backing Decimal256 and other >128-bit integer types)always fell back to a generic bit-by-bit binary long-division loop that iterates
~Bits times (256 shift/compare/subtract rounds for a 256-bit value), even when
the operands are small. Decimal256 arithmetic and the CEIL/ROUND
x / 10^krounding paths hit this hot loop constantly with operands that are far narrower
than 256 bits, so the general algorithm dominates the cost.
This adds three stacked fast paths in front of the generic loop, each bit-exact
with it (verified against native
__int128oracles and via q*d+r==n identities),and each writing the remainder back into
numeratorsooperator%stays correct:single native
unsigned __int128divide. Placed first because it is thecheapest and most frequently hit.
x / 10^k): schoolbook word-by-wordlong division, one hardware 128/64 divide per limb -- O(item_count) divides
instead of ~Bits iterations.
divide_knuth()helper implementing Knuth's Algorithm D (Hacker's Delightdivmnu) in base 2^32, which keeps every intermediate product within auint64_t and stays overflow-safe.
Divisors wider than 128 bits and the zero-divisor throw are unchanged and fall
through to the existing generic path. On a fast-path miss the only added cost is
a short limb scan.
Release note
None
Check List (For Author)
be/test/core/wide_integer_test.cpp(18 new cases:single-limb, two-limb Knuth, both-fit-128, signed, boundary, divide-by-zero,
and randomized differential/ground-truth fuzz against native __int128).
All 23 WideInteger tests pass locally (ASAN build).
the previous slow path)