-
Notifications
You must be signed in to change notification settings - Fork 4k
ARROW-12744: [C++][Compute] Add rounding kernel #10349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
6a6e01e to
49f232b
Compare
|
@bkietz @jorisvandenbossche Need feedback on this PR. Specifically, the rounding options provided and kernel implementations. |
402ab23 to
0240f37
Compare
|
There are 2 round functions ( For tests, I wanted to use the Also, I could not find a way to create the generator dispatchers without explicitly using the @lidavidm @bkietz Any comments or suggestions would be gladly appreciated. |
e7f5d07 to
bf4d80d
Compare
|
There seem to be some Windows-specific test failures :/ |
d58bc48 to
8c3943c
Compare
lidavidm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks good to me. I left two small comments.
lidavidm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this LGTM.
7ef8e65 to
dfe6de2
Compare
|
Should you undraft it or is it still WIP? |
|
It is basically complete and undrafted. There are a few minor comments I made w.r.t. to doubts that I have. |
14d6244 to
3e82fd0
Compare
pitrou
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! A bunch of comments below.
3e82fd0 to
14382ff
Compare
14382ff to
dd001b9
Compare
|
Ready for review cc @pitrou |
|
Looks great! |
|
@rok It is a possibility, if we have the semantics pinned down w.r.t. how to shift specific timestamps (forward, backward, delta). This would be a new set of compute functions: "round_time" and "round_time_to_multiple", where the latter could be a quaternary/varargs function to support multiples for hour, min, sec, ms/ns. |
Indeed that would probably need some work. Does this currently support arbitrary rounding to multiple on timestamps? If yes it might be good to limit it to timezoneless and UTC timestamps to avoid ambiguous and nonexistent timestamp issues. |
lidavidm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks fine to me. One minor comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this just return OptionsWrapper::Init like below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is RoundOptionsWrapper so that its constructor is invoked via make_unique, which initializes pow10 data member. If we use OptionsWrapper then pow10 is not available. I tried invoking OptionsWrapper::Init but it returns a std::unique_ptr which would require "casting" to RoundOptionsWrapper first and then to KernelState to match return type. The unique_ptr casting caused too many issues so I reverted to mimic the OptionsWrapper::Init method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I used these OptionsWrapper classes:
- considers
Initfor options validation becauseInitcan return aStatus::Invalid - constructor for initializing non-options state, which can then be accessed in kernels'
Callviactx->state()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I templetized Init method of KernelState derived classes so that derived constructors can be invoked without the need to duplicate the Init definition. This makes KernelState be fully functional to support options validation and extending kernel state via constructor which can most likely benefit other scalar kernels as well.
For example by extending, OptionsWrapper as follows:
template <typename OptionsType>
struct OptionsWrapper : public KernelState {
template <typename KernelStateType = OptionsWrapper>
static Result<std::unique_ptr<KernelState>> Init(KernelContext* ctx, const KernelInitArgs& args) {
if (auto options = static_cast<const OptionsType*>(args.options)) {
return ::arrow::internal::make_unique<KernelStateType>(*options);
}
...
}
...
};now we can extend custom states as follows:
struct RoundOptionsWrapper<RoundOptions> : public OptionsWrapper<RoundOptions> {
using OptionsType = RoundOptions;
using State = RoundOptionsWrapper<OptionsType>;
double pow10;
explicit RoundOptionsWrapper(OptionsType options) : OptionsWrapper(std::move(options)) {
pow10 = RoundUtil::Pow10(std::abs(options.ndigits));
}
static Result<std::unique_ptr<KernelState>> Init(KernelContext* ctx, const KernelInitArgs& args) {
return OptionsWrapper<OptionsType>::Init<State>(ctx, args);
}
};There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I tried the template variant of OptionsWrapper and although it made the code cleaner, it failed to compiled for some systems, so reverted to duplicating the OptionsWrapper::Init definition. I think there needs to be a refactoring of KernelState and related parts to support validating kernel options and extending kernel state in a simpler manner. There are different patterns being used in the code to fulfill these. But this is a separate issue from this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for the record, it feels a bit weird to call GenerateArithmeticRound at kernel execution time. That said, a quick benchmarking in Python shows there doesn't seem to be any large overhead:
>>> import pyarrow as pa, pyarrow.compute as pc
>>> floor = pc.get_function("floor")
>>> round = pc.get_function("round")
>>> arr = pa.array([None], type=pa.float64())
>>> %timeit floor.call([arr])
2.57 µs ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit floor.call([arr])
2.58 µs ± 11.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit round.call([arr])
2.65 µs ± 10.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit round.call([arr])
2.53 µs ± 12.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. The purpose of doing this here is to generate dispatchers once prior to kernel invocation. Previously, I tried 2 solutions:
- Have a vector of precompute
GenerateArithmeticFloatingPointand use theoptions.round_modeto index this vector. But this required vector to be ordered identically to the round modes inenum RoundMode. - Similar to above but using an
unordered_mapindexed byoptions.round_mode. This requires adding hash support forRoundModedata type. This approach does not imposes a full constraint on the ordering ofRoundMode.
Which one do you think is best?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it is doing the same thing as the other kernels. Other kernels pass exec (an ArrayKernelExec) to the AddKernel method without invoking it. exec is invoked during kernel dispatching because it requires KernelContext, ExecBatch, and Datum parameters.
|
@rok This PR only supports rounding for basic arithmetic data types (unsigned/signed int and floating-point). |
|
@edponce looks like you need to rebase again here as well. |
d6b909d to
65cb707
Compare
0496356 to
302c5f1
Compare
|
Are there any additional comments/reviews? cc @pitrou @bkietz @jorisvandenbossche |
pitrou
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! I'll push a few minor changes and will merge.
302c5f1 to
f91a1a5
Compare
f91a1a5 to
ae740e3
Compare
This PR adds rounding compute functions, namely "round" and "round_to_multiple". * `round(x, RoundOptions(ndigits, round_mode))` - round `x` to the precision indicated by `ndigits` * `round_to_multiple(x, RoundToMultipleOptions(multiple, round_mode))` - round `x` to scale of `multiple` Rounding modes supported are: DOWN, UP, TOWARDS_ZERO, TOWARDS_INFINITY, HALF_DOWN, HALF_UP, HALF_TOWARDS_ZERO, HALF_TOWARDS_INFINITY, HALF_TO_EVEN, HALF_TO_ODD. By default tie-breaking modes round to the nearest integer and resolve ties with HALF_TO_EVEN. The rounding functions expect floating-point inputs and return output of the same type. Integral inputs are implicitly type-casted and output is float64. Closes apache#10349 from edponce/ARROW-12744-Add-rounding-kernel Authored-by: Eduardo Ponce <edponce00@gmail.com> Signed-off-by: Antoine Pitrou <antoine@python.org>
This PR adds rounding compute functions, namely "round" and "round_to_multiple".
round(x, RoundOptions(ndigits, round_mode))- roundxto the precision indicated byndigitsround_to_multiple(x, RoundToMultipleOptions(multiple, round_mode))- roundxto scale ofmultipleRounding modes supported are: DOWN, UP, TOWARDS_ZERO, TOWARDS_INFINITY, HALF_DOWN, HALF_UP, HALF_TOWARDS_ZERO, HALF_TOWARDS_INFINITY, HALF_TO_EVEN, HALF_TO_ODD.
By default tie-breaking modes round to the nearest integer and resolve ties with HALF_TO_EVEN.
The rounding functions expect floating-point inputs and return output of the same type. Integral inputs are implicitly type-casted and output is float64.