Skip to content
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

Move MemoryAddress::copy #169

Closed
wants to merge 3 commits into from

Conversation

@mcimadamore
Copy link
Collaborator

mcimadamore commented May 15, 2020

As discussed, this patch moves MemoryAddress::copy as an instance method of MemorySegment, namely MemorySegment::copyFrom.
I chose the copyFrom suggestion from John, since it allows us more freedom, in the future, to add overloads for different sources for the copy operation.

I've rewrorked the documentation a bit to speak about segment offsets rather than addresses.

I believe all use cases touched by this change actually lead to simpler code, which is good.

I've also added a benchmark for some of the bulk operations such as fill and copyFrom - the numbers I've got look good:

Benchmark             Mode  Cnt       Score       Error  Units
BulkOps.segment_copy  avgt   30  441675.750 ? 12794.881  ns/op
BulkOps.segment_fill  avgt   30  122465.908 ?  1760.711  ns/op
BulkOps.unsafe_copy   avgt   30  430153.694 ?  9055.253  ns/op
BulkOps.unsafe_fill   avgt   30  120413.079 ?  3799.484  ns/op

Cheers


Progress

  • Change must not contain extraneous whitespace
  • Change must be properly reviewed

Reviewers

  • Chris Hegarty (chegar - no project role) ⚠️ Review applies to 3776b9e
  • Jorn Vernee (jvernee - Committer) ⚠️ Review applies to 3776b9e

Download

$ git fetch https://git.openjdk.java.net/panama-foreign pull/169/head:pull/169
$ git checkout pull/169

@bridgekeeper
Copy link

bridgekeeper bot commented May 15, 2020

👋 Welcome back mcimadamore! A progress list of the required criteria for merging this PR into foreign-memaccess will be added to the body of your pull request.

@openjdk openjdk bot added the rfr label May 15, 2020
@mlbridge
Copy link

mlbridge bot commented May 15, 2020

Webrevs

* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
Comment on lines 7 to 9

This comment has been minimized.

@JornVernee

JornVernee May 15, 2020 Member

Wrong copyright

Suggested change
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
* published by the Free Software Foundation.
@openjdk
Copy link

openjdk bot commented May 15, 2020

@mcimadamore This change now passes all automated pre-integration checks, type /integrate in a new comment to proceed. After integration, the commit message will be:

Move MemoryAddress::copy

Reviewed-by: chegar, jvernee
  • If you would like to add a summary, use the /summary command.
  • To credit additional contributors, use the /contributor command.
  • To add additional solved issues, use the /issue command.

Since the source branch of this PR was last updated there have been 2 commits pushed to the foreign-memaccess branch:

  • 3487383: Move MemoryAddress::copy
  • 928a8af: Move "owner" field and thread-confinement checks to MemoryScope

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid automatic rebasing, please merge foreign-memaccess into your branch, and then specify the current head hash when integrating, like this: /integrate 3487383884a0c2af1fa58186e771b54598c92805.

➡️ To integrate this PR with the above commit message to the foreign-memaccess branch, type /integrate in a new comment.

@openjdk openjdk bot added the ready label May 15, 2020
@mcimadamore
Copy link
Collaborator Author

mcimadamore commented May 15, 2020

/integrate

@openjdk openjdk bot closed this May 15, 2020
@openjdk openjdk bot added integrated and removed ready rfr labels May 15, 2020
@openjdk
Copy link

openjdk bot commented May 15, 2020

@mcimadamore The following commits have been pushed to foreign-memaccess since your change was applied:

  • 928a8af: Move "owner" field and thread-confinement checks to MemoryScope

Your commit was automatically rebased without conflicts.

Pushed as commit 3487383.

Copy link
Member

PaulSandoz left a comment

I like it. I always thought the prior location of copy was a little odd, but it took another method for it to really stand out to me.

@@ -308,6 +309,29 @@
*/
MemorySegment fill(byte value);

/**
* Perform bulk copy from given source segment to this segment. More specifically, the bytes at

This comment has been minimized.

@PaulSandoz

PaulSandoz May 15, 2020 Member

"Performs a bulk copy..."

* this segment at offset {@code 0} through {@code src.byteSize() - 1}.
* <p>
* The result of a bulk copy is unspecified if, in the uncommon case, the source segment does not overlap with
* this segment, but it instead refers to an overlapping regions of the same backing storage using different addresses.

This comment has been minimized.

@PaulSandoz

PaulSandoz May 15, 2020 Member

"The result of a bulk copy is unspecified if, in the uncommon case, the source segment and this segment do not overlap, but refer to overlapping regions of the same backing storage using different addresses"

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void unsafe_fill() {
unsafe.setMemory(unsafe_addr, ALLOC_SIZE, (byte)0);

This comment has been minimized.

@PaulSandoz

PaulSandoz May 15, 2020 Member

Out of an abundance of caution you might wanna choose a non-zero value just in case the JIT knows it's zeroing already zeroed memory (i suspect it does not).

@mlbridge
Copy link

mlbridge bot commented May 16, 2020

Mailing list message from John Rose on panama-dev:

Nice work; thank you.

When working with an address a, you often want to copy
memory to or from that address, and (independently)
before or after that address. In C the corresponding
pointer operations would be of form like this:

*p = *q; // copy element at (= just after) pointer
p[0] = q[0]; // ditto
p[i] = q[j]; // copy indexed elements near p and q
*p++ = *q++; // same, with auto-increment
*--p = *--q; // copy element just before pointer + auto-incr.

I guess the Panama code for such operations would contain
expressions like this:

p.segment().asSlice(a.segmentOffset(), (some size)).copyFrom(q?)

What are the natural idioms that might benefit the user,
who wants to get smoothly from a MA p to a copyFrom operation?

It seems to me that one possible primitive would be sized slice
creator:

MemoryAddress sliceAfter(long size) {
return segment().asSlice(segmentOffset(), size);
}

That is good for source operands, since the ?size? value needs to
be attached to the MA in order to get the right slice, and copyFrom
takes the size from the source, not the destination.

For sources of the form *--q, maybe this also:

MemoryAddress sliceBefore(long size) {
return segment().asSlice(segmentOffset() - size, size);
}

Or maybe this combo:

MemoryAddress sliceAt(int index, long size) {
return segment().asSlice(segmentOffset() + (size * index), size);
}

Then sliceAt(0,s) == sliceAfter(s) and sliceAt(-1,s) == sliceBefore(s).
That suggests that the latter forms (sliceAfter(s)) are redundant,
and that sliceAt is the interesting primitive.

Another use case is an *unsized* slice creator for destination operands.
This allows the sizing information to flow naturally (without redundancy)
from the data source through copyFrom:

MemoryAddress sliceAfter() {
return segment().asSlice(segmentOffset(), segment().byteSize() - segmentOffset());
}

Maybe for symmetry, and for cutting off buffers after a fill pointer, this too:

MemoryAddress sliceBefore() {
return segment().asSlice(0, segmentOffset());
}

Anyway, I like the way MS is shaping up, as a small bounded location of
state, and MA as a working pointer therein. You all have probably had
similar thoughts about ?what sort of sugary methods we might eventually
place on MA?. The placement of MS::copyOf unlocked some of those ideas
for me, and the above is what I came up with, FWIW.

? John

@mlbridge
Copy link

mlbridge bot commented May 16, 2020

Mailing list message from John Rose on panama-dev:

On May 15, 2020, at 6:19 PM, John Rose <john.r.rose at oracle.com> wrote:

Nice work; thank you.

When working with an address a, you often want to copy
memory to or from that address, and (independently)
before or after that address. In C the corresponding
pointer operations would be of form like this:

*p = *q; // copy element at (= just after) pointer
p[0] = q[0]; // ditto
p[i] = q[j]; // copy indexed elements near p and q
*p++ = *q++; // same, with auto-increment
*--p = *--q; // copy element just before pointer + auto-incr.

I guess the Panama code for such operations would contain
expressions like this:

p.segment().asSlice(a.segmentOffset(), (some size)).copyFrom(q?)

What are the natural idioms that might benefit the user,
who wants to get smoothly from a MA p to a copyFrom operation?

It seems to me that one possible primitive would be sized slice
creator:

MemoryAddress sliceAfter(long size) {
return segment().asSlice(segmentOffset(), size);
}

That is good for source operands, since the ?size? value needs to
be attached to the MA in order to get the right slice, and copyFrom
takes the size from the source, not the destination.

For sources of the form *--q, maybe this also:

MemoryAddress sliceBefore(long size) {
return segment().asSlice(segmentOffset() - size, size);
}

Or maybe this combo:

MemoryAddress sliceAt(int index, long size) {
return segment().asSlice(segmentOffset() + (size * index), size);
}

Then sliceAt(0,s) == sliceAfter(s) and sliceAt(-1,s) == sliceBefore(s).
That suggests that the latter forms (sliceAfter(s)) are redundant,
and that sliceAt is the interesting primitive.

Another use case is an *unsized* slice creator for destination operands.
This allows the sizing information to flow naturally (without redundancy)
from the data source through copyFrom:

MemoryAddress sliceAfter() {
return segment().asSlice(segmentOffset(), segment().byteSize() - segmentOffset());
}

Maybe for symmetry, and for cutting off buffers after a fill pointer, this too:

MemoryAddress sliceBefore() {
return segment().asSlice(0, segmentOffset());
}

Anyway, I like the way MS is shaping up, as a small bounded location of
state, and MA as a working pointer therein. You all have probably had
similar thoughts about ?what sort of sugary methods we might eventually
place on MA?. The placement of MS::copyOf unlocked some of those ideas
for me, and the above is what I came up with, FWIW.

? John

P.S. A little more: One idiom I didn?t cover was copying
something before the destination pointer, a in this:

*--p = *q

Here, a user will reach for an expression like this:

p.addOffset(-(some size)).copyFrom(q.sliceAfter(same size));

The need to repeat the same size in two places is a breeding
ground for bugs and non-readable code. It seems like we would
want a method on MA which takes a source MS and *then* creates
a destination MS based on the MS and the size of the *source*.
That?s not needed for copying something *after* a pointer,
but it is needed for copying *before*.

Something like this would help. The destination pointer can
be returned for optional use:

MemoryAddress copyBeforeFrom(MemorySegment src) {
MemoryAddress predec = addOffset(src.byteSize());
predec.sliceAfter().copyFrom(src);
return predec;
}

For symmetry, there could be this also:

MemoryAddress copyAfterFrom(MemorySegment src) {
MemoryAddress postinc = addOffset(src.byteSize());
this.sliceAfter().copyFrom(src);
return postinc;
}

@mlbridge
Copy link

mlbridge bot commented May 18, 2020

Mailing list message from Maurizio Cimadamore on panama-dev:

On 16/05/2020 02:19, John Rose wrote:

Anyway, I like the way MS is shaping up, as a small bounded location of
state, and MA as a working pointer therein. You all have probably had
similar thoughts about ?what sort of sugary methods we might eventually
place on MA?. The placement of MS::copyOf unlocked some of those ideas
for me, and the above is what I came up with, FWIW.

These are all interesting ideas - thanks John.

I'd like to think some more and to see how they fit with the slicing
methods we already have.

For instance, sliceAt could be expressed as an overload of
MemorySegment::asSlice which, in addition to the usual offset/length
parameters also takes a base address (and throws is the address does not
belong to the segment).

A feeling I have is that one of the reasons for going back and forth
through addresses, is, perhaps caused by two factors:

* cannot pass negative offset in MemroySegment::asSlice (that's because
a slice doesn't know, currently, the offset of its parent)
* cannot have unsized slices (e.g. MemorySegment::asSlice which takes
only one parameter - the offset)
* cannot pass a MemoryAddress directly instead of a byte offset

My sense is that, since copyFrom is expressed in _segments_ having ways
to slice segments directly more naturally (rather than to call
baseAddress() and then do the operation on the address) might be
slightly better for client code. E.g. let's take the example you have
written:

p.segment().asSlice(a.segmentOffset(), (some size)).copyFrom(q?)

In most code I've seen, you already start off with two segments (so `p`
is a segment too), and the really annoying bit is to have to specify a
size which is going inevitably to be some dependent expression - either
on `a.segmentOffset() - baseAddress()` or on `q`.

So, I believe that if we offered a no-length overload:

p_segment.asSlice(a.segmentOffset()).copyFrom(q?)

Things would already improve considerably in the common case (remember,
in such cases, at least the ones we've seen, `p` is a segment already).
Which kind of leaves out the case where you want a negative offset too.

That said, I think that it would also be nice to provide overloads that,
rather than taking bytes, it would just take a MA:

p_segment.asSlice(a).copyFrom(q?)

Although this method could now throw (if the address does not belong to
the segment we're slicing).

So I think I see an incremental path to get to a more sugary final state
- the big question is gonna be how we're going to deal with slicing
segments using negative offsets. This is easily expressed if you start
from an address (like you did in your proposal) - but a tad harder if
you start from segments. That said, given also past experiences, I'd be
wary of putting _some_ slicing methods on `MemorySegment` and others on
`MemoryAddress` as, in the past, that led to poor discoverability of the
API (users didn't know that the MemoryAddress:copy method existed) which
was one of the motivation behind the cleanup + move.

Thanks
Maurizio

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants
You can’t perform that action at this time.