Skip to content
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

8341127: Extra call to MethodHandle::asType from memory segment var handles fails to inline #21283

Closed
wants to merge 9 commits into from

Conversation

mcimadamore
Copy link
Contributor

@mcimadamore mcimadamore commented Oct 1, 2024

The fix for JDK-8331865 introduced an accidental performance regression.
The main issue is that now all memory segment var handles go through some round of adaptation.
Adapting a var handle results in a so called indirect var handle.
When an indirect var handle is called through a var handle guard, an extra MethodHandle::asType call is triggered.
In some cases, if asType has already been compiled into a big method, it cannot be inlined into the caller, which then results in a failure to inline through the var handle call, resulting in a big performance regression.

The solution is to make sure that the compiled size of MethodHandle::asType stays small: this is done by making sure that the slowpath (the one which populates the cache used by asType) is not inlined by the JVM. This is done by consolidating the slow path into a separate method, which is annotated with the internal @DontInline annotation.

This problem was originally reported here:
https://mail.openjdk.org/pipermail/panama-dev/2024-September/020643.html

While we did not test this fix directly, we have made sure that running the problematic benchmark with the flags:

-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.setAsTypeCache
-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.asTypeUncached

Solves the performance regression. The fix in this PR is just a programmatic way to achieve the same results w/o the need of command line flags.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8341127: Extra call to MethodHandle::asType from memory segment var handles fails to inline (Enhancement - P2)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/21283/head:pull/21283
$ git checkout pull/21283

Update a local copy of the PR:
$ git checkout pull/21283
$ git pull https://git.openjdk.org/jdk.git pull/21283/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 21283

View PR using the GUI difftool:
$ git pr show -t 21283

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/21283.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 1, 2024

👋 Welcome back mcimadamore! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.


static {
types = new Class<?>[TYPE_SIZE];
ClassLoader customLoader = new URLClassLoader(new URL[0], LoopOverNonConstantAsType.class.getClassLoader());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically, using a different class loadrer is not required - but using classes with different class loaders can end up taking even longer code paths when calling MethodHandle::asType, so I've added that here.

@openjdk
Copy link

openjdk bot commented Oct 1, 2024

@mcimadamore 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:

8341127: Extra call to MethodHandle::asType from memory segment var handles fails to inline

Reviewed-by: psandoz, redestad, jvernee

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 278 new commits pushed to the master branch:

  • bdfb41f: 8309841: Jarsigner should print a warning if an entry is removed
  • 57c1db5: 8332697: ubsan: shenandoahSimpleBitMap.inline.hpp:68:23: runtime error: signed integer overflow: -9223372036854775808 - 1 cannot be represented in type 'long int'
  • dc0ce1b: 8341336: Fix -Wzero-as-null-pointer-constant warnings in PRODUCT-only code
  • c43202b: 8341037: Use standard layouts in DefaultFrameIconTest.java and MenuCrash.java
  • 76283dd: 8341246: Test com/sun/tools/attach/PermissionTest.java fails access denied after JDK-8327114
  • 0bdfe88: 8328313: Archived module graph should allow identical --module-path to be specified during dump time and run time
  • 9fc1c68: 8339850: Restore the interrupt status in FileSystemPreferences.lockFile()
  • 5063494: 8340785: Update description of PassFailJFrame and samples
  • 85f0442: 8317116: Provide layouts for multiple test UI in PassFailJFrame
  • 49501fe: 8341412: Various test failures after JDK-8334305
  • ... and 268 more: https://git.openjdk.org/jdk/compare/0c36177fead8b64a4cee9da3c895e3799f8ba231...master

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 master branch, type /integrate in a new comment.

@openjdk openjdk bot added the rfr Pull request is ready for review label Oct 1, 2024
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(org.openjdk.jmh.annotations.Scope.Thread)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 3, jvmArgsAppend = { "-XX:-TieredCompilation" })
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The benchmark only reproduces if tiered compilation is disabled. This is due to the fact that some threshold are updated accordingly, and set to a smaller value, which is enough to exhibit the issue.

@openjdk
Copy link

openjdk bot commented Oct 1, 2024

@mcimadamore The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Oct 1, 2024
@mlbridge
Copy link

mlbridge bot commented Oct 1, 2024

Webrevs

@mcimadamore
Copy link
Contributor Author

Benchmark results

Before:

Benchmark                               (asTypeCompiled)  Mode  Cnt  Score   Error  Units
LoopOverNonConstantAsType.segment_loop             false  avgt   30  0.232 ± 0.003  ms/op
LoopOverNonConstantAsType.segment_loop              true  avgt   30  6.943 ± 0.100  ms/op
LoopOverNonConstantAsType.unsafe_loop              false  avgt   30  0.223 ± 0.002  ms/op
LoopOverNonConstantAsType.unsafe_loop               true  avgt   30  0.227 ± 0.002  ms/op

After:

Benchmark                               (asTypeCompiled)  Mode  Cnt  Score   Error  Units
LoopOverNonConstantAsType.segment_loop             false  avgt   30  0.237 ± 0.002  ms/op
LoopOverNonConstantAsType.segment_loop              true  avgt   30  0.233 ± 0.003  ms/op
LoopOverNonConstantAsType.unsafe_loop              false  avgt   30  0.233 ± 0.002  ms/op
LoopOverNonConstantAsType.unsafe_loop               true  avgt   30  0.234 ± 0.002  ms/op

@mcimadamore
Copy link
Contributor Author

Alternatives considered:

  1. don't inline slow path (implemented in this PR)
  2. add @ForceInline to MethodHandle::asType
  3. add extra check on var handle guards to skip redundant asType calls

Of these, (1) was preferred as it fixes the underlying issue (and not just for memory segment var handles), while at the same time avoiding unwanted side-effects (e.g. adding @ForceInline can often result in too much code getting compiled, which can then create issues with other callee-side thresholds).

Copy link
Member

@cl4es cl4es left a comment

Choose a reason for hiding this comment

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

LGTM

In the micro perhaps the unsafe_loop variant is superfluous for this special case test? At least it's distinct from the test in LoopOverNonConstant by summing longs rather than ints.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Oct 1, 2024
@mcimadamore
Copy link
Contributor Author

LGTM

In the micro perhaps the unsafe_loop variant is superfluous for this special case test? At least it's distinct from the test in LoopOverNonConstant by summing longs rather than ints.

Yeah - that is a bit redundant - however could be useful to have some sort of baseline, so I left it in there.

@@ -885,7 +886,9 @@ private MethodHandle asTypeCached(MethodType newType) {
return null;
}

private MethodHandle setAsTypeCache(MethodHandle at) {
@DontInline
Copy link
Member

Choose a reason for hiding this comment

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

Could you leave a comment explaining why we put DontInline here?

}
}

@CompilerControl(CompilerControl.Mode.DONT_INLINE)
Copy link
Member

Choose a reason for hiding this comment

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

I think the intent was to block inlining of asType, so it gets compiled in isolation? That should be done with a CompileCommand though. This annotation just blocks inlining of compileAsType AFAIK.

Copy link
Contributor Author

@mcimadamore mcimadamore Oct 1, 2024

Choose a reason for hiding this comment

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

I see what you mean... I'll do some experiments and see how the current annotation affects the benchmark - if at all.

Copy link
Contributor Author

@mcimadamore mcimadamore Oct 1, 2024

Choose a reason for hiding this comment

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

The issue with using a compile command is that it will then work globally. So the benchmark would not really test much - besides checking that var handle access is slow when asType cannot inline. What I was trying to do here was to avoid asType being inlined into that compileAsType method - but I agree that the annotation I added isn't it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe EXCLUDE is what I was reaching out for

Copy link
Member

Choose a reason for hiding this comment

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

I think EXCLUDE would work, yes. True about CompileCommand having a global effect, so that won't work. (With a CompilerDirective file we could be more precise, not sure if it's worth it though).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the latest iteration, I removed the annotation. Doesn't seem to be making any difference.

…tantAsType.java

Co-authored-by: Jorn Vernee <JornVernee@users.noreply.github.com>
@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Oct 1, 2024
@openjdk openjdk bot added the ready Pull request is ready to be integrated label Oct 1, 2024
@iwanowww
Copy link
Contributor

iwanowww commented Oct 1, 2024

I don't have a clear understanding how VarHandleGuards is used, but I assume that MethodHandle::asType calls there are no-ops in scenarios you care about.

It does make sense to annotatesetAsTypeCache with @DontInline, but I'd also add @ForceInline on asType.

@mcimadamore
Copy link
Contributor Author

I don't have a clear understanding how VarHandleGuards is used, but I assume that MethodHandle::asType calls there are no-ops in scenarios you care about.

A var handle guard is a piece of code that detects var handle call of a certain shape, to avoid creation of lambda forms in some common cases. This was done in Java 9, and was unchanged by FFM. In this case, we just happen to hit the var handle guard for (LJ -> J). And yes, the asType adaptation is a no-op here.

The difference is that in the normal code path (lambda form), the call to asType is guarded by a check:

https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/invoke/Invokers.java#L513

Normally, var handle guards don't need an asType adaptation:

https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/invoke/VarHandleGuards.java#L230

But if a var handle guard is triggered on an "indirect" var handle (read: a var handle that has been adapted with some combinator - this part is new since FFM), then we go through a different path in the guard code which always calls asType:

https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/invoke/VarHandleGuards.java#L233

(note: this "slow path" inside var handle guards predates FFM, but var handle adaptation has increased the number of cases in which we hit this slow path).

The PR that introduced this regression made all FFM var handles undergo some adaptation. Which is why now we're seeing this issue - as we're now hitting a (no-op) asType call which was never made before.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Oct 2, 2024
@openjdk openjdk bot added the ready Pull request is ready to be integrated label Oct 2, 2024
@mcimadamore
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Oct 4, 2024

Going to push as commit 7fa2f22.
Since your change was applied there have been 306 commits pushed to the master branch:

  • 1bdd79e: 8341261: Tests assume UnlockExperimentalVMOptions is disabled by default
  • ec020f3: 8340426: ZGC: Move defragment out of the allocation path
  • a63ac5a: 8340792: -XX:+PrintInterpreter: instructions should only be printed if printing all InterpreterCodelets
  • 3f420fa: 8341451: Remove C2HandleAnonOMOwnerStub
  • d3139b4: 8341000: Open source some of the AWT Window tests
  • 4ded283: 8338136: Hotspot should support multiple large page sizes on Windows
  • 10402b4: 8341489: ProblemList runtime/cds/appcds/DumpRuntimeClassesTest.java in Xcomp mode
  • 6bc3971: 8341316: [macos] javax/swing/ProgressMonitor/ProgressMonitorEscapeKeyPress.java fails sometimes in macos
  • e89fd1d: 8341128: open source some 2d graphics tests
  • 6f459af: 8340077: Open source few Checkbox tests - Set2
  • ... and 296 more: https://git.openjdk.org/jdk/compare/0c36177fead8b64a4cee9da3c895e3799f8ba231...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Oct 4, 2024
@openjdk openjdk bot closed this Oct 4, 2024
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Oct 4, 2024
@openjdk
Copy link

openjdk bot commented Oct 4, 2024

@mcimadamore Pushed as commit 7fa2f22.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@mcimadamore
Copy link
Contributor Author

/backport :jdk23

@openjdk
Copy link

openjdk bot commented Oct 4, 2024

@mcimadamore the backport was successfully created on the branch backport-mcimadamore-7fa2f229-jdk23 in my personal fork of openjdk/jdk. To create a pull request with this backport targeting openjdk/jdk:jdk23, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 7fa2f229 from the openjdk/jdk repository.

The commit being backported was authored by Maurizio Cimadamore on 4 Oct 2024 and was reviewed by Paul Sandoz, Claes Redestad and Jorn Vernee.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk:

$ git fetch https://github.com/openjdk-bots/jdk.git backport-mcimadamore-7fa2f229-jdk23:backport-mcimadamore-7fa2f229-jdk23
$ git checkout backport-mcimadamore-7fa2f229-jdk23
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk.git backport-mcimadamore-7fa2f229-jdk23

⚠️ @mcimadamore You are not yet a collaborator in my fork openjdk-bots/jdk. An invite will be sent out and you need to accept it before you can proceed.

@mcimadamore
Copy link
Contributor Author

/backport jdk23u

@openjdk
Copy link

openjdk bot commented Oct 4, 2024

@mcimadamore the backport was successfully created on the branch backport-mcimadamore-7fa2f229-master in my personal fork of openjdk/jdk23u. To create a pull request with this backport targeting openjdk/jdk23u:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 7fa2f229 from the openjdk/jdk repository.

The commit being backported was authored by Maurizio Cimadamore on 4 Oct 2024 and was reviewed by Paul Sandoz, Claes Redestad and Jorn Vernee.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk23u:

$ git fetch https://github.com/openjdk-bots/jdk23u.git backport-mcimadamore-7fa2f229-master:backport-mcimadamore-7fa2f229-master
$ git checkout backport-mcimadamore-7fa2f229-master
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk23u.git backport-mcimadamore-7fa2f229-master

⚠️ @mcimadamore You are not yet a collaborator in my fork openjdk-bots/jdk23u. An invite will be sent out and you need to accept it before you can proceed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

5 participants