RFR: 8207851 JEP Draft: Support ByteBuffer mapped over non-volatile memory
Stuart Marks
stuart.marks at oracle.com
Fri Sep 28 05:51:45 UTC 2018
Hi Andrew,
Let me first stay that this issue of "ByteBuffer might not be the right answer"
is something of a digression from the JEP discussion. I think the JEP should
proceed forward using MBB with the API that you and Alan had discussed
previously. At most, the discussion of the "right thing" issue might affect a
side note in the JEP text about possible limitations and future directions of
this effort. However, it's not a blocker to the JEP making progress as far as
I'm concerned.
With that in mind, I'll discuss the issue of multithreaded access to ByteBuffers
and how this bears on whether buffers are or aren't the "right answer." There
are actually several issues that figure into the "right answer" analysis. In
this message, though, I'll just focus on the issue of multithreaded access.
To recap (possibly for the benefit of other readers) the Buffer class doc has
the following statement:
Buffers are not safe for use by multiple concurrent threads. If a buffer
is to be used by more than one thread then access to the buffer should be
controlled by appropriate synchronization.
Buffers are primarily designed for sequential operations such as I/O or codeset
conversion. Typical buffer operations set the mark, position, and limit before
initiating the operation. If the operation completes partially -- not uncommon
with I/O or codeset conversion -- the position is updated so that the operation
can be resumed easily from where it left off.
The fact that buffers not only contain the data being operated upon but also
mutable state information such as mark/position/limit makes it difficult to have
multiple threads operate on different parts of the same buffer. Each thread
would have to lock around setting the position and limit and performing the
operation, preventing any parallelism. The typical way to deal with this is to
create multiple buffer slices, one per thread. Each slice has its own
mark/position/limit values but shares the same backing data.
We can avoid the need for this by adding absolute bulk operations, right?
Let's suppose we were to add something like this (considering ByteBuffer only,
setting the buffer views aside):
get(int srcOff, byte[] dst, int dstOff, int length)
put(int dstOff, byte[] src, int srcOff, int length)
Each thread can perform its operations on a different part of the buffer, in
parallel, without interference from the others. Presumably these operations
don't read or write the mark and position. Oh, wait. The existing absolute put
and get overloads *do* respect the buffer's limit, so the absolute bulk
operations ought to as well. This means they do depend on shared state. (I guess
we could make the absolute bulk ops not respect the limit, but that seems
inconsistent.)
OK, let's adopt an approach similar to what was described by Peter Levart a
couple messages upthread, where a) there is an initialization step where various
things including the limit are set properly; b) the buffer is published to the
worker threads properly, e.g., using a lock or other suitable memory operation;
and c) all worker threads agree only to use absolute operations and to avoid
relative operations.
Now suppose the threads have completed their work and you want to, say, write
the buffer's contents to a channel. You have to carefully make sure the threads
are all finished and properly publish their results back to some central thread,
have that central thread receive the results, set the position and limit, after
which the central thread can initiate the I/O operation.
This can certainly be made to work.
But note what we just did. We now have an API where:
- there are different "phases", where in one phase all the methods work, but
in another phase only certain methods work (otherwise it breaks silently);
- you have to carefully control all the code to ensure that the wrong methods
aren't called when the buffer is in the wrong phase (otherwise it breaks
silently); and
- you can't hand off the buffer to a library (3rd party or JDK) without
carefully orchestrating a transition into the right phase (otherwise it breaks
silently).
Frankly, this is pretty crappy. It's certainly possible to work around it.
People do, and it is painful, and they complain about it up and down all day
long (and rightfully so).
Note that this discussion is based primarily on looking at the ByteBuffer API. I
have not done extensive investigation of the impact of the various buffer views
(IntBuffer, LongBuffer, etc.), nor have I looked thoroughly at the
implementations. I have no doubt that we will run into additional issues when we
do those investigations.
If we were designing an API to support multi-threaded access to memory regions,
it would almost certainly look nothing like the buffer API. This is what Alan
means by "buffers might not be the right answer." As things stand, it appears
quite difficult to me to fix the multi-threaded access problem without turning
buffers into something they aren't, or fragmenting the API in some complex and
uncomfortable way.
Finally, note that this is not an argument against adding bulk absolute
operations! I think we should probably go ahead and do that anyway. But let's
not fool ourselves into thinking that bulk absolute operations solve the
multi-threaded buffer access problem.
s'marks
More information about the hotspot-compiler-dev
mailing list