-
Notifications
You must be signed in to change notification settings - Fork 5.8k
8332522: SwitchBootstraps::mappedEnumLookup constructs unused array #19906
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
👋 Welcome back jlahoda! A progress list of the required criteria for merging this PR into |
@lahodaj This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 211 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
int sum = 0; | ||
for (E e : inputs) { | ||
sum += switch (e) { | ||
case null -> -1; |
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.
As this null
case adds a case relative to the -Traditional
test then maybe removing one of the E0, E1, ...
cases would make the test a little bit more apples-to-apples?
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.
Using case null ->
will push javac to use the new code, but all switches do some kind of null check for the selector value. The difference is that if there's no case null
, there will be Objects.requireNonNull
generated for the selector value (which will throw an NPE if the value is null
), while here it will jump to the given case.
So, case null
does not have the same weight as a normal case, so I don't think it would be fair to remove a full case to compensate for it.
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.
Fair enough, and I guess ~1.6ns/op is reasonable overhead for the added semantics.
@@ -289,20 +280,16 @@ public static CallSite enumSwitch(MethodHandles.Lookup lookup, | |||
labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray(); |
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.
You could create EnumDesc[] enumDescLabels
here and remove the Arrays.copyOf
on line 290. The labels.clone()
on line 277 also seem redundant since we only iterate over the labels
argument once.
While this case is likely fine I generally recommend using a minimal amount of streams/lambdas in bootstrap sensitive code like these dynamic bootstraps methods.
@Stable | ||
public int[] map; | ||
public volatile MethodHandle generatedSwitch; |
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.
We probably don't need these 2 volatiles:
- Arrays are already safely published (See https://bugs.openjdk.org/browse/JDK-8333791?focusedId=14680594&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14680594) and we can only access array elements after the array is fully initialized then published. Thus it's a safe publication, and reads don't require explicit volatile read.
MethodHandle
is immutable and safely published, thus volatile read is redundant as well.
generatedSwitch = | ||
generateTypeSwitch(lookup, enumClass, labels) | ||
.asType(MethodType.methodType(int.class, | ||
enumClass, |
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.
We might have to do Object.class
so we can call invokeExact
below
src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
Outdated
Show resolved
Hide resolved
….java Co-authored-by: Chen Liang <liach@openjdk.org>
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.
Since labels
is no longer eagerly cloned, it’s important to store the converted labels into a local array to avoid leaking them to user code.
boolean constantsOnly = true; | ||
int len = labels.length; | ||
|
||
for (int i = 0; i < len; i++) { | ||
Object convertedLabel = | ||
convertEnumConstants(lookup, enumClass, labels[i]); | ||
labels[i] = convertedLabel; | ||
if (constantsOnly) | ||
constantsOnly = EnumDesc.class.isAssignableFrom(convertedLabel.getClass()); | ||
} |
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.
boolean constantsOnly = true; | |
int len = labels.length; | |
for (int i = 0; i < len; i++) { | |
Object convertedLabel = | |
convertEnumConstants(lookup, enumClass, labels[i]); | |
labels[i] = convertedLabel; | |
if (constantsOnly) | |
constantsOnly = EnumDesc.class.isAssignableFrom(convertedLabel.getClass()); | |
} | |
boolean constantsOnly = true; | |
int len = labels.length; | |
Object[] convertedLabels = new Object[len]; | |
for (int i = 0; i < len; i++) { | |
Object convertedLabel = | |
convertEnumConstants(lookup, enumClass, labels[i]); | |
convertedLabels[i] = convertedLabel; | |
if (constantsOnly) | |
constantsOnly = EnumDesc.class.isAssignableFrom(convertedLabel.getClass()); | |
} |
EnumDesc<?>[] enumDescLabels = | ||
Arrays.copyOf(labels, labels.length, EnumDesc[].class); |
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.
EnumDesc<?>[] enumDescLabels = | |
Arrays.copyOf(labels, labels.length, EnumDesc[].class); | |
EnumDesc<?>[] enumDescLabels = | |
Arrays.copyOf(convertedLabels, len, EnumDesc[].class); |
target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0); | ||
EnumDesc<?>[] enumDescLabels = | ||
Arrays.copyOf(labels, labels.length, EnumDesc[].class); | ||
target = MethodHandles.insertArguments(StaticHolders.MAPPED_ENUM_SWITCH, 2, lookup, enumClass, enumDescLabels, new MappedEnumCache()); | ||
} else { | ||
target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels); |
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.
target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels); | |
target = generateTypeSwitch(lookup, invocationType.parameterType(0), convertedLabels); |
True. But it is easier and cleaner, IMO, to simply put back cloning of the labels. |
Any input on the current version of the patch? |
convertEnumConstants(lookup, enumClass, labels[i]); | ||
labels[i] = convertedLabel; | ||
if (constantsOnly) | ||
constantsOnly = EnumDesc.class.isAssignableFrom(convertedLabel.getClass()); |
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.
Feels a bit weird when we can use convertedLabel instanceof EnumDesc
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 great to me, thanks for the cleanup!
/integrate |
Going to push as commit 958786b.
Your commit was automatically rebased without conflicts. |
For general pattern matching switches, the
SwitchBootstraps
class currently generates a cascade ofif
-like statements, computing the correct target case index for the given input.There is one special case which permits a relatively easy faster handling, and that is when all the case labels case enum constants (but the switch is still a "pattern matching" switch, as tranditional enum switches do not go through
SwitchBootstraps
). Like:We can create an array mapping the runtime ordinal to the appropriate case number, which is somewhat similar to what javac is doing for ordinary switches over enums.
The
SwitchBootstraps
class was trying to do so, when the restart index is zero, but failed to do so properly, so that code is not used (and does not actually work properly).This patch is trying to fix that - when all the case labels are enum constants, an array mapping the runtime enum ordinals to the case number will be created (lazily), for restart index == 0. And this map will then be used to quickly produce results for the given input. E.g. for the case above, the mapping will be
{0 -> 0, 1 -> 2, 2 -> 1}
(meaning{A -> 0, B -> 2, C -> 1}
).When the restart index is != 0 (i.e. when there's a guard in the switch, and the guard returned
false
), the if cascade will be generated lazily and used, as in the general case. If it would turn out there are significant enum-only switches with guards/restart index != 0, we could improve there as well, by generating separate mappings for every (used) restart index.I believe the current tests cover the code functionally sufficiently - see
SwitchBootstrapsTest.testEnums
. It is only that the tests do not (and regression tests cannot easily, I think) differentiate whether the special-case or generic implementation is used.I've added a new microbenchmark attempting to demonstrate the difference. There are two benchmarks, both having only enum constants as case labels. One,
enumSwitchTraditional
is an "old" switch, desugared fully by javac, the other,enumSwitchWithBootstrap
is an equivalent switch that uses theSwitchBootstraps
. Before this patch, I was getting values like:and with this patch:
So, this seems like a clear improvement to me.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/19906/head:pull/19906
$ git checkout pull/19906
Update a local copy of the PR:
$ git checkout pull/19906
$ git pull https://git.openjdk.org/jdk.git pull/19906/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 19906
View PR using the GUI difftool:
$ git pr show -t 19906
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/19906.diff
Webrev
Link to Webrev Comment