Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Optimize divisions and remainders by constants.#5167

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
sjrd wants to merge1 commit intoscala-js:main
base:main
Choose a base branch
Loading
fromsjrd:opt-divide-and-remainder

Conversation

@sjrd
Copy link
Member

@sjrdsjrd commentedMay 10, 2025
edited
Loading

Following the techniques described in Hacker's Delight, Chapter 10.

Benchmarking suggested that is this generally worth it, except for non-powers of 2 for a) int operations on JS and b) long operations on Wasm.

For the long divisions on JS, this yields a 6x speedup, despite the fact that the resulting code needs 16 (!) primitive int multiplications.

plokhotnyuk and He-Pin reacted with rocket emoji
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch 2 times, most recently fromb0ca504 to0ef8849CompareJune 2, 2025 15:34
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch from0ef8849 to560a48cCompareJune 13, 2025 16:38
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch 3 times, most recently fromeee96e7 to864ebaeCompareJune 23, 2025 15:23
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch 4 times, most recently from3eac5c3 to615ec6aCompareJuly 15, 2025 15:24
@sjrdsjrd marked this pull request as ready for reviewJuly 15, 2025 15:25
@sjrdsjrd requested a review fromgzm0July 15, 2025 15:25
@sjrd
Copy link
MemberAuthor

@gzm0 This PR is now ready for review. It sent me through many detours before I could finish it. :p

@sjrdsjrdforce-pushed theopt-divide-and-remainder branch 4 times, most recently from95a590f tobe78b6aCompareJuly 15, 2025 16:43
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch frombe78b6a to04771e4CompareJuly 25, 2025 08:49
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch 2 times, most recently from32113aa to467dfceCompareSeptember 7, 2025 13:27
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch from467dfce to048e31fCompareSeptember 29, 2025 08:04
Copy link
Contributor

@gzm0gzm0 left a comment

Choose a reason for hiding this comment

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

I'm releasing a partial review to share some progress.

The overall architecture looks good, I'm mostly still trying to understand the relevant pieces of Hacker's Delight so I can cross match them with the code.

All files that do not have an explicit comment are fully reviewed at this point.

* be available when we link a javalib < 1.20.
*
* Its user-land IR is easy enough to hard-code. However, we lose the
* handling as a RuntimeLOng intrinsic done by the optimizer.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* handlingasaRuntimeLOng intrinsic done by the optimizer.
* handlingasaRuntimeLong intrinsic done by the optimizer.

sjrd reacted with thumbs up emoji
implicitscope:Scope):TailRec[Tree]= {
implicitvalpos= pretrans.pos

defexpand(methodName:MethodName,targs:PreTransform*):TailRec[Tree]= {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
defexpandLongOp(methodName:MethodName,targs:PreTransform*):TailRec[Tree]= {

(for me, the diff renders wrong, I mean renameexpand ->expandLongOp).

sjrd reacted with thumbs up emoji
privatevalintrinsics=
Intrinsics.buildIntrinsics(config.coreSpec.esFeatures, isWasm)

privatelazyvalintegerDivisions=
Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC the ctor doesn't do anything.

Suggested change
privatelazyvalintegerDivisions=
privatevalintegerDivisions=

sjrd reacted with thumbs up emoji
}

valnegativeDivisor= isSigned&& int.lt(divisor, int.zero)
valabsDivisor=if (negativeDivisor) int.negate(divisor)else divisor
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
valabsDivisor=if (negativeDivisor)int.negate(divisor)else divisor
valabsDivisor= int.abs(divisor)

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

No, that would be incorrect.int.abs would unconditionally treat negative numbers as such. But ifisSigned is false, "negative" numbers must be treated as positive, and therefore must not be given toabs! Which is whynegativeDivisor hasisSigned &&.

Comment on lines 67 to 72
val (isSigned, isLong)= opmatch {
caseBinaryOp.Int_/|BinaryOp.Int_%=> (true,false)
caseBinaryOp.Long_/|BinaryOp.Long_%=> (true,true)
caseBinaryOp.Int_unsigned_/|BinaryOp.Int_unsigned_%=> (false,false)
caseBinaryOp.Long_unsigned_/|BinaryOp.Long_unsigned_%=> (false,true)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
val (isSigned, isLong)= opmatch {
caseBinaryOp.Int_/|BinaryOp.Int_%=> (true,false)
caseBinaryOp.Long_/|BinaryOp.Long_%=> (true,true)
caseBinaryOp.Int_unsigned_/|BinaryOp.Int_unsigned_%=> (false,false)
caseBinaryOp.Long_unsigned_/|BinaryOp.Long_unsigned_%=> (false,true)
}
valisLong= int.bitSize==64

Then, with the suggestion below, you can entirely dropop from the arguments.

sjrd reacted with thumbs up emoji
for (k<-1 to63)
test(1L<< (64- k),0,0,1L<< k)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Note to self: continue here.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

The split destroyed this information. It was located below the magic tests, but above the shape tests and fuzz tests.

test(0x2aaaaaaaaaaaaaabL,0,1,12L)
test(0xa3d70a3d70a3d70bL,1,4,25L)
test(0x20c49ba5e353f7cfL,0,4,125L)
test(0x346dc5d63886594bL,0,7,625L)
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO: Understand why add is here but not on HD.


// -----------------------------------------------------------------
// --- Testing the specific shape of trees for selected divisors ---
// -----------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you provide some insight in the value these tests provide? It feels that the code they exercise (after unit tests of magic) is not so complicated to warrant an additional test layer before the fuzzer.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

These tests notably ensures that the best path is taken for each category. For example, that we don't produce the generic path for powers of 2.

Also, I found them to be the most useful while writing the code. When something goes wrong (other than the magic number computations), these are the test that directly show you what went wrong and what to fix.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: Incomplete review (up to continue here comment).

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: Incomplete review (up to continue here comment).

@gzm0gzm0 self-requested a reviewOctober 19, 2025 19:51
@gzm0
Copy link
Contributor

Note: I've immediately re-requested a review from myself, so this PR shows up in my review list.

@sjrdsjrdforce-pushed theopt-divide-and-remainder branch from048e31f to832b3dfCompareOctober 26, 2025 08:09
Copy link
MemberAuthor

@sjrdsjrd left a comment

Choose a reason for hiding this comment

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

Addressed comments so far, I believe.


// -----------------------------------------------------------------
// --- Testing the specific shape of trees for selected divisors ---
// -----------------------------------------------------------------
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

These tests notably ensures that the best path is taken for each category. For example, that we don't produce the generic path for powers of 2.

Also, I found them to be the most useful while writing the code. When something goes wrong (other than the magic number computations), these are the test that directly show you what went wrong and what to fix.

}

valnegativeDivisor= isSigned&& int.lt(divisor, int.zero)
valabsDivisor=if (negativeDivisor) int.negate(divisor)else divisor
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

No, that would be incorrect.int.abs would unconditionally treat negative numbers as such. But ifisSigned is false, "negative" numbers must be treated as positive, and therefore must not be given toabs! Which is whynegativeDivisor hasisSigned &&.

test(0x2aaaaaab,0,1,12)
test(0x51eb851f,0,3,25)
test(0x10624dd3,0,3,125)
test(0x68db8bad,0,8,625)
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

TBH I was surprised that they did not include them in HD. They are an integral part of the magic computation, IMO.

Following the techniques described in Hacker's Delight, Chapter 10.Benchmarking suggested that is this generally worth it, except fornon-powers of 2 for a) int operations on JS and b) long operationson Wasm.For the long divisions on JS, this yields a 6x speedup, despite thefact that the resulting code needs 16 (!) primitive intmultiplications.
@sjrdsjrdforce-pushed theopt-divide-and-remainder branch from832b3df to51166acCompareOctober 26, 2025 08:16
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@gzm0gzm0Awaiting requested review from gzm0

Requested changes must be addressed to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@sjrd@gzm0

[8]ページ先頭

©2009-2025 Movatter.jp