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

[SYCL] Subbuffer CopyBack Data - (REVIEW) #2085

Closed
wants to merge 2 commits into from

Conversation

cperkinsintel
Copy link
Contributor

optimization so that when (only) a subbuffer is used, the entire buffer is not copied back.

Signed-off-by: Chris Perkins [email protected]

optimization so that when (only) a subbuffer is used, the entire buffer is not copied back.

Signed-off-by: Chris Perkins <[email protected]>
@cperkinsintel cperkinsintel requested review from againull and a team as code owners July 10, 2020 04:23
q.wait();

// change readBuffer behind its back.
clear_arr(baseData);
Copy link
Contributor

@mfbalin mfbalin Jul 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the SYCL 2020 provisional specs, it says on page 123, second paragraph that "2. A buffer can be constructed with associated host memory and a default buffer allocator. The buffer will use
this host memory for its full lifetime, but the contents of this host memory are unspecified for the lifetime
of the buffer. If the host memory is modified by the host, or mapped to another buffer or image during the
lifetime of this buffer, then the results are undefined. The initial contents of the buffer will be the contents
of the host memory at the time of construction."

Does this mean that this test has undefined behaviour by SYCL specs because it doesn't use an accessor to access and modify host data that is still in use by a buffer?

I am also wondering whether it is still an undefined behavior to read the host data when its buffer is only accessed via read-accessors throughout its lifetime. The specs say that it is undefined behavior when we try to modify it but it doesn't state whether my use case is allowed or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's UB to access host data passed to the buffer c'tor until the buffer is destroyed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cperkinsintel Please, rework the test to avoid UB.

Comment on lines +52 to +53
if (Mode == access::mode::read || Mode == access::mode::discard_write ||
Mode == access::mode::discard_read_write)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why discard_write mode isn't a write mode? According to SYCL spec:
access::mode::discard_write | Write-only access. Previous contents discarded

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't an exported function. The question isAWriteMode is answering here is "Is this mode one that would have written and that we now need to worry about?". And even though discard_writes do write, we don't need to worry about them - they don't need to propagated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the function should be renamed then because current name is very confusing.

@romanovvlad romanovvlad marked this pull request as draft July 13, 2020 13:39
Copy link
Contributor

@romanovvlad romanovvlad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of the comments.

@@ -60,81 +60,91 @@ class buffer {
buffer(const range<dimensions> &bufferRange,
const property_list &propList = {})
: Range(bufferRange) {
size_t SizeInBytes = get_count() * sizeof(T);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
size_t SizeInBytes = get_count() * sizeof(T);
const size_t SizeInBytes = get_count() * sizeof(T);

make_unique_ptr<detail::SYCLMemObjAllocatorHolder<AllocatorT>>());
impl->recordBufferUsage(((void *)this), SizeInBytes, 0, false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add a comment for what literal values stand for, like /*ImprovePerformance2x*/false

Comment on lines +241 to +242
IsSubBuffer = rhs.IsSubBuffer;
OffsetInBytes = rhs.OffsetInBytes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, move to initializer list.

};

// need to track information about a sub/buffer,
// even after its destruction, we may need to know about it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want to know about it after sub/buffer destruction?

Copy link
Contributor Author

@cperkinsintel cperkinsintel Jul 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sub-buffers handle the copy-back of their own data. They will have been destroyed before the buffer_impl is destroyed. And when it is destroyed it needs to know if it has to initiate a copy back or not. So before it can answer that question it needs to know if there were any sub-buffers and if they actually performed any copy operations (ie had any write accessors declared).

@@ -108,6 +108,7 @@ class __SYCL_EXPORT stream_impl {
GlobalOffsetAccessorT accessGlobalOffset(handler &CGH) {
auto OffsetSubBuf = buffer<char, 1>(Buf, id<1>(0), range<1>(OffsetSize));
auto ReinterpretedBuf = OffsetSubBuf.reinterpret<unsigned, 1>(range<1>(1));
ReinterpretedBuf.set_write_back(false); // Buf handles write back.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this change? The spec says:
The reinterpreted SYCL buffer that is constructed must behave as though it were a copy of the SYCL buffer...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. And it's a copy of a sub-buffer which, with this new addition, would initiate a copy back on its destruction. The stream implementation is using two accessors, one on the base buffer, and one on the sub-buffer/reinterp. And the reinterp is going out of scope at the end of this function. Don't need it's destructor to initiate a copy back - that's already handled by the base in this case.
I've been thinking about changing how this new sub-buffer dtor code works with reinterprted buffers, such that if a sub-buffer is reinterpreted, then we just go back to the existing system where everything is handled by the base buffer.

for (int i = offset; i < offset + subbuf_size; ++i)
assert(vec[i] == (i < offset + offset_inside_subbuf ? i * 10 : i * -10) &&
"Invalid result in 1d sub buffer");
for (int i = 0; i < size; ++i) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the test fail without the patch?


void ensureNoUnecessaryCopyBack(queue &q) {

std::cout << "start ensureNoUnecessaryCopyBack" << std::endl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, include iostream if this is used.

std::cout << "start ensureNoUnecessaryCopyBack" << std::endl;

//allocate memory
int *baseData = (int *)(malloc(total * sizeof(int)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use std::vector or std::array.

// But we don't care about the write buffer. We only care that the read buffer was NOT copied-back.
{ // closure
//setup and clear memory
setup_arr(baseData); // [0, 1, 2, ..., total]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use std::iota instead.

q.wait();

// change readBuffer behind its back.
clear_arr(baseData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cperkinsintel Please, rework the test to avoid UB.

@romanovvlad
Copy link
Contributor

@sergey-semenov Could you review?

@sergey-semenov sergey-semenov self-requested a review July 22, 2020 10:55
@cperkinsintel
Copy link
Contributor Author

This version is the "shortened" one, in that it only deals with copy-backs (not limiting sub-buffer memcpy, for example). So I think it might be a bit overengineered for what it is. I'm going to make a pass at simplifying it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants