andygrove commented on code in PR #5082:
URL: https://github.com/apache/datafusion-comet/pull/5082#discussion_r3692485403


##########
native/spark-expr/src/math_funcs/round.rs:
##########
@@ -59,6 +59,29 @@ macro_rules! integer_round {
     }};
 }
 
+// Round a single native integer when `10^(-point)` does not fit in the native
+// integer type but still fits in i128. The caller has already excluded the
+// case where `div` fits the native type, so `|x| < div` and the result is
+// either `0` (when `|x| < half`) or `sign(x) * div` — the latter always
+// overflows the native type. Under ANSI we throw, under legacy we wrap by
+// truncation (matches `BigDecimal.longValue`'s low-64-bit semantics).
+macro_rules! integer_round_widened {
+    ($X:expr, $DIV:expr, $HALF:expr, $NATIVE:ty, $FAIL_ON_ERROR:expr) => {{
+        let x128 = $X as i128;
+        if x128 > -$HALF && x128 < $HALF {
+            Ok(0 as $NATIVE)
+        } else if $FAIL_ON_ERROR {
+            Err(ArrowError::ComputeError(
+                arithmetic_overflow_error("integer").to_string(),
+            ))
+        } else if x128 >= $HALF {
+            Ok($DIV as $NATIVE)
+        } else {
+            Ok((-$DIV) as $NATIVE)
+        }
+    }};
+}

Review Comment:
   I tried this and I would like to push back, with numbers.
   
   I implemented it exactly as described — one `integer_round!` that always 
computes `rem`/`half` in i128 and does a single checked-or-wrapping downcast at 
the end, deleting `integer_round_widened!` and reducing the dispatch to two 
branches (still need the `10^(-point)` doesn't-fit-i128 fallback, since 
`checked_pow` returns `None` there and we have no `div` to divide by). It is 
genuinely nicer to read, and it dissolved your other three comments on this 
macro for free. But it costs 2.3-3.3x on these kernels:
   
   | Benchmark | native + widened | unified i128 | Change |
   | --- | --- | --- | --- |
   | int64 scale=-1 legacy | 16.0 µs | 42.9 µs | **+168%** |
   | int64 scale=-1 ansi | 15.8 µs | 42.8 µs | **+171%** |
   | int64 scale=-9 legacy | 18.6 µs | 43.3 µs | **+132%** |
   | int64 scale=-19 legacy | 14.7 µs | 48.0 µs | **+226%** |
   | int32 scale=-1 legacy | 15.2 µs | 40.5 µs | **+166%** |
   | int32 scale=-1 ansi | 15.2 µs | 43.3 µs | **+185%** |
   
   (criterion, 8192 rows, same machine, baseline re-measured back-to-back and 
reproducing within 3%, and the regression reproduced on a second run.)
   
   Two causes. On the native band, `x % div` becomes a 128-bit division, which 
on arm64 and x86-64 is an `__umodti3`/`__udivti3` libcall rather than a single 
instruction. On the widened band it is worse: today that band does **no 
division at all** — the `|x| <= NATIVE::MAX < div` invariant makes `x % div == 
x`, so it is just two comparisons — whereas the unified version pays a 128-bit 
modulo by a divisor near 1e19, the most expensive shape there is.
   
   So I have kept the two macros, but taken the substance of your comment, 
which I think is the real point: the invariant was load-bearing and only stated 
in prose. It is now (a) spelled out as `|x| <= NATIVE::MAX < div`, with the `x 
% div == x` consequence and the fact that no division is needed made explicit, 
and (b) enforced by a `debug_assert!` inside `integer_round_widened!`, so any 
future caller that violates it fails loudly in tests rather than silently 
computing the wrong thing. Tests run in debug, so this is live coverage.
   
   The comparison to `decimal_round_f` I think cuts the other way, 
incidentally: it computes in i128 because its input *is* an i128 unscaled 
value, so there is no narrower type to fall back to. Here the input is 
i8/i16/i32/i64 and the narrow path is the common case, so paying i128 
unconditionally is a real cost rather than a free simplification.
   
   I have added `native/spark-expr/benches/round.rs` (alongside the existing 
`floor`/`ceil` benches) in 285a211bd so this is measurable rather than asserted 
— happy to be overruled if you still prefer the single path, but I did not want 
to take a 2-3x hit on the kernel silently.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to