8000 Opt: Use unsigned arithmetics in Range, instead of Longs. by sjrd · Pull Request #5176 · scala-js/scala-js · GitHub
[go: up one dir, main page]

Skip to content

Opt: Use unsigned arithmetics in Range, instead of Longs. #5176

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

Merged
merged 4 commits into from
May 25, 2025

Conversation

sjrd
Copy link
Member
@sjrd sjrd commented May 22, 2025

Previously, Range used a number of intermediate operations on Longs to avoid overflow. The optimizer was already good enough to remove them in many cases, in particular when the step is 1 and/or the start is 0, which are common cases.

Now that the optimizer can reason about unsigned division, we can do a lot better. We can entirely avoid Long computations, and streamline a lot of code, by using unsigned Int arithmetics. This produces much better code for the cases where the optimizer cannot entirely get rid of the computations.


Diffs of the test suite in 2.12 and 2.13 can be seen here (I trimmed away all the local variable name changes):
https://gist.github.com/sjrd/e270bc3acaf86526b86767a4d107b409

My favorite diff is the following, happening for a range by 2:

   $x_1.require__Z__V((((this$2.length % 2) | 0) === 0));
   var this$4 = $n(s1);
   var end = this$4.length;
-  var isEmpty = (end <= 0);
   var isEmpty$1 = (end <= 0);
-  if (isEmpty$1) {
-  } else {
-    var hi$2 = (end >> 31);
-    var this$10 = $m_RTLong$();
-    var lo = this$10.divideImpl__I__I__I__I__I(end, hi$2, 2, 0);
-    var hi$3 = this$10.RTLong$__f_org$scalajs$linker$runtime$RuntimeLong$$hiReturn;
-    var hi$4 = (end >> 31);
-    var this$12 = $m_RTLong$();
-    this$12.remainderImpl__I__I__I__I__I(end, hi$4, 2, 0);
-  }
-  var hi$8 = (end >> 31);
-  var this$16 = $m_RTLong$();
-  var lo$3 = this$16.remainderImpl__I__I__I__I__I(end, hi$8, 2, 0);
-  var scala$collection$immutable$Range$$lastElement$1 = ((lo$3 !== 0) ? ((end - lo$3) | 0) : (((-2) + end) | 0));
+  var div = ((end >>> 1) | 0);
+  var scala$collection$immutable$Range$$numRangeElements = (((div << 1) !== end) ? ((1 + div) | 0) : div);
+  var n = (((-1) + scala$collection$immutable$Range$$numRangeElements) | 0);
+  var scala$collection$immutable$Range$$lastElement$1 = (n << 1);
   if ((!isEmpty$1)) {
     var i = 0;
     while (true) {

Turned 3 Long division operations into a few int shifts. 👌

@sjrd sjrd requested a review from gzm0 May 22, 2025 15:05
@sjrd sjrd force-pushed the range-unsigned-ints-instead-of-longs branch 3 times, most recently from 297f137 to 09bbab0 Compare May 23, 2025 09:59
Copy link
Contributor
@gzm0 gzm0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Only optional comments.

(if (step >= 0) start > end else start < end)
else
(if (step >= 0) start >= end else start <= end)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this diff to a separate commit? Seems (a-priori) unrelated to moving to unsigned arithmetic.

I understand that optimizing isEmpty becomes more important because of the changed definition of numRangeElements. So in that sense, it is clearly related.

I understand how it (likely) improves codesize: isInclusive is highly likely to be known constant, so the other branch can be eliminated.

* standard unsigned int encoding of the mathematical value otherwise.
*
* This interpretation allows to represent all values with the correct
* modular arithmetics, which streamlines the use sites.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: usage sites?

/** Creates a new range containing the first `n` elements of this range.
*
* $doesNotUseBuilders
*
* @param n the number of elements to take.
* @return a new range consisting of `n` first elements.
*/
final override def take(n: Int): Range = (
final override def take(n: Int): Range =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (also below): use braces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the style here is to enclose a single if expr in braces, but braces signal that there are statements, so that is not my learned preference. ("Learned" as in "not innate", not as in "overeducated".) (Unbraced is easier with checked indentation or -Wnonunit-statement. Parens served that purpose of ensuring there is just an expression.)

sjrd added 2 commits May 25, 2025 17:52
Extract branches to be mostly dependent on `isInclusive` and
`step >= 0`. These are often constant, and when they're not
statically constant, they are probably predictable anyway.
Previously, `Range` used a number of intermediate operations on
`Long`s to avoid overflow. The optimizer was already good enough
to remove them in many cases, in particular when the step is `1`
and/or the start is `0`, which are common cases.

Now that the optimizer can reason about unsigned division, we can
do a lot better. We can entirely avoid `Long` computations, *and*
streamline a lot of code, by using unsigned `Int` arithmetics.
This produces much better code for the cases where the optimizer
cannot entirely get rid of the computations.
@sjrd sjrd force-pushed the range-unsigned-ints-instead-of-longs branch from 09bbab0 to d972218 Compare May 25, 2025 15:56
@sjrd sjrd enabled auto-mer 86F0 ge May 25, 2025 16:06
@sjrd sjrd merged commit 29e02bf into scala-js:main May 25, 2025
3 checks passed
@sjrd sjrd deleted the range-unsigned-ints-instead-of-longs branch May 25, 2025 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0