8279527: Dereferencing segments backed by different scopes leads to pollution #82
Conversation
|
} catch (ScopedMemoryAccess.Scope.ScopedAccessError ex) { | ||
throw new IllegalStateException("This segment is already closed"); | ||
} | ||
scope.checkValidStateSlow(); |
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.
Not to be confused with ResourceScope::checkValidState
- this method is only really used by other non-performance critical code around AbstractMemorySegmentImpl
.
@mcimadamore The following labels will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
public abstract void checkValidState(); | ||
@ForceInline | ||
public final void checkValidState() { | ||
if (owner != null && owner != Thread.currentThread()) { |
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.
For consistency we could change code checkValidStateSlow
to refer directly to owner
.
It would be satisfying, but I don't know if it's possible, to compose checkValidStateSlow
from checkValidState
e.g.
public final checkValidStateSlow() {
checkValidState();
if (!isAlive() { ... }
}
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.
I'll change use of owner
. It's not really possible to write checkValidStateSlow in terms of checkValidState, because the latter does a plain read of the state, whereas the former does a volatile read. Reusing one from the other would result in two reads (a plain and a volatile).
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.
Ok. My thought was that since this is slow two reads do not matter, but i did not reason fully about the concurrent implications (if the fast alive check returns false, the slow alive check can still return true so that seems good, if the fast check returns true i was presume the slow alive check would also be true, given the way state changes monotonically?)
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.
If we're ok with a redundant plain read, then I don't think there are issues. You just do two reads, and the latter (the volatile one) is the one that counts. I don't think we can rely much on dependencies between what the plain read and what the volatile read will see. The state is updated in both direction (for shared segments) e.g. we can go from ALIVE to CLOSING then back to ALIVE. Or we could go from ALIVE to CLOSING to CLOSE.
That said, I guess my main reservation for writing one routine on top of the other is that we really want checkValidState to be only used in critical hot paths. It has a non-volatile semantics and an exception handling which only really makes sense when combined with ScopedMemoryAccess - for this reason, using it as an internal building primitive didn't seem to me as a great idea.
@mcimadamore This change now passes all automated pre-integration checks. 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 5 new commits pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the
|
@@ -39,7 +40,6 @@ | |||
*/ | |||
final class ConfinedScope extends ResourceScopeImpl { | |||
|
|||
private boolean closed; // = false | |||
private int lockCount = 0; | |||
private int asyncReleaseCount = 0; | |||
private final Thread owner; |
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.
Is this owner
field still needed now that the super class also has 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.
Forgot to remove - well spotted
* Reuse `state` variable for confined lock count
/integrate |
Going to push as commit d65c665.
Your commit was automatically rebased without conflicts. |
@mcimadamore Pushed as commit d65c665. |
This patch fixes a performance issue when dereferencing memory segments backed by different kinds of scopes. When looking at inline traces, I found that one of our benchmark, namely
LoopOverPollutedSegment
was already hitting the ceiling of the bimorphic inline cache, specifically when checking liveness of the segment scope in the memory access hotpath (ResourceScopeImpl::checkValidState
). The benchmark only used segments backed by confined and global scope. I then added (in the initialization "polluting" loop) segments backed by a shared scope, and then the benchmark numbers started to look as follows:That is, since now we have three different kinds of scopes (confined, shared and global), the call to the liveness check can no longer be inlined. One solution could be, as we do for the base accessor, to add a scope accessor to all memory segment implementation classes. But doing so only works ok for heap segments (for which the scope accessor just returns the global scope constants). For native segments, we're still megamorphic (as a native segment can be backed by all kinds of scopes).
In the end, it turned out to be much simpler to just make the liveness check monomorphic, since there's so much sharing between the code paths already. With that change, numbers of the tweaked benchmark go back to normal:
Note that this patch tidies up a bit the usage of
checkValidState
vs.checkValidStateSlow
. The former should only really be used in the hot path, while the latter is a more general routine which should be used in non-performance critical code. MakingcheckValidState
monomorphic caused theScopeAccessError
to be generated in more places, so I needed to either update the usage to use the safercheckValidStateSlow
(where possible) or, (inBuffer
andConfinedScope
) just add extra wrapping.Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk18 pull/82/head:pull/82
$ git checkout pull/82
Update a local copy of the PR:
$ git checkout pull/82
$ git pull https://git.openjdk.java.net/jdk18 pull/82/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 82
View PR using the GUI difftool:
$ git pr show -t 82
Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jdk18/pull/82.diff