[foreign-memaccess] RFC: to scope or not to scope?
Jorn Vernee
jbvernee at xs4all.nl
Tue Jun 4 00:04:34 UTC 2019
Maurizio Cimadamore schreef op 2019-06-04 01:39:
> On 03/06/2019 23:13, Jorn Vernee wrote:
>> Maurizio Cimadamore schreef op 2019-06-03 22:54:
>>> On 01/06/2019 16:50, Jorn Vernee wrote:
>>>> For asConfined() creating a view segment would not work, since the
>>>> root segment, which holds the liveliness flag, would not be confined
>>>> to a single thread. For asConfined to work as expected I believe we
>>>> would have to kill the root segment, effectively transferring the
>>>> underlying resource to the newly created confined segment. This also
>>>> brings up an idea I had been thinking about; make thread
>>>> confined-ness a more local, temporary state. e.g. you call
>>>> asConfined which creates a segment that is confined to the current
>>>> thread, but then add a release() method that invalidates the
>>>> confined copy and makes the root segment valid again. This could be
>>>> used around loops, e.g.:
>>>
>>> I don't think I agree with this line of thought. The use I have in
>>> mind for this API is something like:
>>>
>>> try (MemorySegment segment =
>>> MemorySegment.ofNative(bytes).asConfined()) { ... }
>>>
>>> If you do this, then inside the try with resources you have the
>>> guarantee that everything will be confined.
>>
>> This seems to be excluding the case where the MemorySegment is stored
>> in a field, and then used in a loop. I'd deem that case quite likely,
>> and I think there's a solution possible that works for both use-cases
>> without adding too much complexity.
>
> Can't you do same for field?
>
> MemorySegment segment = MemorySegment.ofNative(bytes).asConfined();
>
>
> If you do this, who exactly will be able to access the non-confined
> temporary root we have created?
>
>>
>> Also, when we have a snippet like this, the liveliness check should
>> already be hoisted, assuming the loop body is also inlined, since the
>> executing thread is the only one that has access to the liveliness
>> flag.
>>
>>> We could of course enforce a no-access state by default, or
>>> confined-by-default, but IMHO that's just adding additional hops for
>>> the (likely) case where these things don't make a difference. And, if
>>> you really want to make sure that what you have is confined, and
>>> there's no racy access possible, just call the view method as soon as
>>> you allocate the segment (as above), then nobody will be able to
>>> access to the non-confined version.
>>>
>>> This is not different from how ByteBuffer::asReadOnly works - and I
>>> think that's a pragmatic compromise. That API too will have the same
>>> issues discussed here (if somebody keeps hold of the original BB, you
>>> can still write on a supposedly read-only buffer), but I wonder how
>>> much that is really a problem in practice, given that it can be
>>> avoided with some discipline.
>>
>> Yes, but the JIT can not really assume that the programmer has this
>> discipline. I'm not sure if we can prove that there's only a single
>> thread accessing a segment (or rather, the liveliness flag) when this
>> is not guaranteed by the API. Which would be the case if a confined
>> segment is simply a view over a non-confined segment I think.
>
> Not sure I follow and this seems to conflate API aspects with
> optimization aspects. From liveness perspective, only one thread can
> change the liveness bit. This is true even in the patch I've sent.
> Each segment has an owner thread, and only the owner can close the
> region. Confinement only add extra enforcements when accessing the
> region with read/write operatons.
>
> Also, from an optimization level, I'm not aware of the JIT having any
> issue of 'not trusting' that every access comes from same thread. The
> JIT doesn't worry about that - and if there are issues, these are
> typically multi-threading bug in user-land (e.g. users forgot to add
> synchronization where they needed to). Vlad can correct me if I'm
> wrong, but that is my understanding.
I thought optimization was the whole point of adding the thread
confinement... From [1]:
> To be able to get to the same level of performances as Unsafe::get/put,
> we need to be able to hoist the liveness check away in the JIT; but
> there's a problem here: even if the JIT can see through a bunch of code
> using addresses allocated by a given scope, it has to conservatively
> assume that another thread might chime in, and close the scope behind
> our back.
>
> This makes it impossible for the JIT to completely optimize away the
> check. For this reason, in my document [...] I posited the existence of
> a 'confined' scope, whose existence is bound to a given thread (and
> maybe, in the future, a fiber).
If we have an intermediate and public state that is non-confined, and
then the confined state is just a view over this non-confined state, the
JIT will still have to conservatively assume that another thread has
access the the non-confined state and can close the scope behind our
back AFAIK. But, if we design our API in such a way that the
non-confined state is not observable this should not be a problem, and
we have a guarantee that a thread confined segment is actually thread
confined.
>>
>>> As much as I agree with the theory that returning a no-access segment
>>> is the only way to make the API really tight, at the same time, I
>>> having factory methods that return what would effectively be unusable
>>> segments seems a weird API choice to me. At this point I think I
>>> would
>>> prefer having some 'flags' to be passed to the segment factory
>>> (pretty
>>> much like we had scope charateristics before).
>>
>> This seems good to me for the current goal we're trying to achieve;
>> making the liveliness check hoist-able. I think of the local acquire +
>> release strategy as a different way to get there.
>
> I think the problems with making the liveliness check hoistable have
> nothing to do with confinement guarantees, really - again I'll defer
> to Vlad for more on this.
>
> To me the #1 problem is a user model one - as a creator and user of a
> segment, do I want to worry about other threads closing the segment
> behind my back? I don't think we want that. And that is what leads to
> having some critical operations being confined. I think the JIT mostly
> doesn't care about all this.
>
>>
>> For fully fledged memory resource sharing between threads I agree with
>> John that we should disallow access by multiple threads at the same
>> time. So the state between allocating a segment and it being acquired
>> by a single thread would be 'useless', but we could use a separate
>> type that doesn't derive from MemorySegment as well for this right?
>> That shouldn't be too weird...
>
> I have problems with this view. On the one hand we have a very rich
> VarHandle API which allows very fine-grained control over concurrent
> access. On the other hand this is saying: sorry, only one thread at a
> time.
>
> There will be cases, for sure, where people will be happy with 'one
> thread at a time'. But there will be other cases where people won't
> care, or where people would like to go racy, and handle things
> themselves (this is, after all, a low level API). I honestly don't see
> the need for such hand-holding decisions here. I think a racy default
> is perfectly fine (after all, that's what users of Unsafe are familiar
> with) - if you want more protection, ask for it (but also know that it
> might come at extra cost).
>
> I'm very worried about second guessing how users might use this very
> general API, and trying to enforce general model of usage - which
> might work well in certain cases, but utterly fail in others. If we
> start simple, sticking with what's in Unsafe, we can build an API that
> works, and the we can add stability guarantees as optional add-ons -
> this way the performance model is clear - you pay what you get. So you
> can stick with raw and racy (and fast), or go with confined and safe
> (and possibly less fast).
I agree with an opt-in for shared segments. This was more of a
hypothetical of how such a thread-safe segment could be implemented :)
Jorn
[1] :
https://mail.openjdk.java.net/pipermail/panama-dev/2019-May/005494.html
> Maurizio
>
>>
>> Jorn
>>
>>> Maurizio
More information about the panama-dev
mailing list