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
8284050: [vectorapi] Optimize masked store for non-predicated architectures #8544
Conversation
👋 Welcome back xgong! A progress list of the required criteria for merging this PR into |
@XiaohongGong 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. |
/label add hotspot-compiler |
@XiaohongGong |
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.
The JIT (in all other circumstances AFAIK) never produces "phantom stores", stores into Java variables which are not specified as the target of a JVM store instruction (putfield, dastore, etc.). The fact that a previously-read value is used by the phantom store does not make it any better.
Yes, the memory states may be correct after the blend and store is done, but the effect on the Java Memory Model is to issue the extra phantom stores of the unselected array elements. Under certain circumstances, this will create race conditions after the optimization where there were no race conditions before the optimization. Other threads could (under Java Memory Model rules) witness the effects of the phantom stores. If the Java program is properly synchronized, the introduction of an illegitimate race condition can cause another thread, now in an illegal race, to see an old value in a variable (the recopied unselected array element) which the JMM says is impossible.
Yes, this only shows up in multi-threaded programs, and ones where two threads step on one array, but Java is a multi-threaded language, and it must conform to its own specification as such.
This blend technique would be very reasonable if there is no race condition. (Except at the very beginning or end of arrays.) And the JMM leaves room for many optimizations. And yet I think this is a step too far. I'd like to be wrong about this, but I don't think I am.
So, I think you need to use a different technique, other than blend-and-unpredicated-store, for masked stores on non-predicated architectures.
For example, you could simulate a masked store instruction on an architecture that supports scatter (scattering values of the array type). Do this by setting up two vectors of machine pointers. One vector points to each potentially-affected element of the array (some kind of index computation plus a scaled iota vector). The other vector is set up similarly, but points into a fixed-sized, thread-local buffer, what I would call the "bit bucket". Blend the addresses, and then scatter, so that selected array lanes are updated, and unselected values are sent to the "bit bucket".
This is complex enough (and platform-dependent enough) that you probably need to write a hand-coded assembly language subroutine, to call from the JIT code. Sort of like the arraycopy stubs.
It's even more work than the proposed patch here, but it's the right thing, I'm afraid.
} else { | ||
// Use the vector blend to implement the masked store. The biased elements are the original | ||
// values in the memory. | ||
Node* mem_val = gvn().transform(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, mem_num_elem, mem_elem_bt)); |
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'm sorry to say it, but I am pretty sure this is an invalid optimization.
See top-level comment for more details.
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.
Thanks for your comments! Yeah, this actually influences something due to the Java Memory Model rules which I missed to consider more. I will try the scatter ways instead. Thanks so much!
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.
Yes, phantom store can write back stale unintended value and may create problem in multithreded applications since blending is done with an older loaded value.
Mailing list message from Hans Boehm on hotspot-dev: Naive question: What happens if one of the vector elements that should not Hans On Wed, May 4, 2022 at 7:08 PM Xiaohong Gong <xgong at openjdk.java.net> wrote: |
Yeah, this is the similar concern with what @rose00 mentioned above. The current solution cannot work well for multi-thread progresses. I will consider other better solutions. Thanks for the comments! |
if (offset >= 0 && offset <= (a.length - vsp.length())) { | ||
intoBooleanArray0(a, offset, m, /* offsetInRange */ true); |
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.
The offset check could save the checkMaskFromIndexSize
for cases that offset are in the valid array bounds, which also improves the performance. @rose00 , do you think this part of change is ok at least? Thanks!
Mailing list message from John Rose on hotspot-dev:
That part is ok, yes. I wish we could get the same effect with loop optimizations but I don?t know an easy way. The explicit check in the source code gives the JIT a crutch but I hope we can figure out a way in the future to integrate mask logic into range check elimination logic, making the crutches unnecessary. For now it?s fine. |
Thanks! So I will separate this part out and fix it in another PR first. For the store masked vectorization with scatter or other ideas, I'm not quite sure whether they can always benefit cross architectures and need more investigation. I prefer to close this PR now. Thanks for all your comments! |
Currently the vectorization of masked vector store is implemented by the masked store instruction only on architectures that support the predicate feature. The compiler will fall back to the java scalar code for non-predicate supported architectures like ARM NEON. However, for these systems, the masked store can be vectorized with the non-masked vector
"load + blend + store"
. For example, storing a vector"v"
controlled by a mask"m"
into a memory with address"addr" (i.e. "store(addr, v, m)")
can be implemented with:Since the first full loading needs the array offset must be inside of the valid array bounds, we make the compiler do the vectorization only when the offset is in range of the array boundary. And the compiler will still fall back to the java scalar code if not all offsets are valid. Besides, the original offset check for masked lanes are only applied when the offset is not always inside of the array range. This also improves the performance for masked store when the offset is always valid. The whole process is similar to the masked load API.
Here is the performance data for the masked vector store benchmarks on a X86 non avx-512 system, which improves about
20x ~ 50x
:Similar performance gain can also be observed on ARM NEON system.
And here is the performance data on X86 avx-512 system, which improves about
1.88x - 2.81x
:Similar performance gain can also be observed on ARM SVE system.
Progress
Integration blocker
Issue
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/8544/head:pull/8544
$ git checkout pull/8544
Update a local copy of the PR:
$ git checkout pull/8544
$ git pull https://git.openjdk.java.net/jdk pull/8544/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 8544
View PR using the GUI difftool:
$ git pr show -t 8544
Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jdk/pull/8544.diff