RFR: 8207851 JEP Draft: Support ByteBuffer mapped over non-volatile memory
peter.levart at gmail.com
Fri Sep 28 07:21:13 UTC 2018
I mostly agree with your assessment about the suitability of the
ByteBuffer API for nice multithreaded use. What would such API look
like? I think pretty much like ByteBuffer but without things that mutate
mark/position/limit/ByteOrder. A stripped-down ByteBuffer API therefore.
That would be in my opinion the most low-level API possible. If you add
things to such API that coordinate multithreaded access to the
underlying memory, you are already creating a concurrent data structure
for a particular set of use cases, which might not cover all possible
use cases or be sub-optimal at some of them. So I think this is better
layered on top of such API not built into it. Low-level multithreaded
access to memory is, in my opinion, always going to be "unsafe" from the
standpoint of coordination. It's not only the
mark/position/limit/ByteOrder that is not multithreaded-friendly about
ByteBuffer API, but the underlying memory too. It would be nice if
mark/position/limit/ByteOrder weren't in the way though.
On 09/28/2018 07:51 AM, Stuart Marks wrote:
> 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
> 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
> 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.
More information about the core-libs-dev