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

Add zero-copy serialization APIs. #357

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

teo-tsirpanis
Copy link
Member

SC-58009

This PR updates the Buffer and BufferList types to implement the io.WriterTo interface, by writing the data directly to an io.Writer without an intermediate copy to Go-backed memory. For Buffer we do that with the Unsafe.Slice function (it is safe as long as the Writer follows the contract and does not retain the slice after the call to Write), and for BufferList we call WriteTo for each buffer.

In order to take advantage of this, new serialization APIs were added that return a Buffer instead of a byte[] (and unavoidably had to make a copy to Go-managed memory). The old serialization APIs, as well as the Buffer.Serialize() and BufferList.Flatten() functions were deprecated.

@teo-tsirpanis teo-tsirpanis requested a review from a team as a code owner November 26, 2024 17:05
Copy link
Contributor

@shaunrd0 shaunrd0 left a comment

Choose a reason for hiding this comment

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

Left some small comments but LGTM, nice work!

buffer.go Outdated
// Keep the buffer alive during the write, to prevent the GC from
// collecting the memory while it's being used.
n, err := w.Write(unsafe.Slice((*byte)(cbuffer), writeSize))
runtime.KeepAlive(b)
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 since b is the object that implements this method the keepalive isn't needed

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed. I was being extra cautious, but if we call b.WriteTo(w), it becomes clear that b is being kept somewhere reachable.

serialize.go Outdated
return &buffer, nil
}

// SerializeArrayNonEmptyDomainToBuffer gets and serializes the array nonempty domain and returns a Buffer object containing the payload.
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
// SerializeArrayNonEmptyDomainToBuffer gets and serializes the array nonempty domain and returns a Buffer object containing the payload.
// SerializeArrayNonEmptyDomain gets and serializes the array nonempty domain.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed, thanks for catching it.

serialize.go Outdated Show resolved Hide resolved
}
assert.NotEmpty(t, bytes.Bytes())
runtime.GC()
runtime.GC()
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand the purpose of the last few lines in this test, but I see there was one like it just below so I'm probably missing something.

Should there be a check here that buffer was garbage collected, or is the test verifying something else with the explicit calls to runtime.GC()?

Copy link
Member Author

Choose a reason for hiding this comment

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

I copied the GC calls from the existing test, I don't know what their purpose was.

buffer_list.go Outdated
@@ -44,6 +45,36 @@ func (b *BufferList) Context() *Context {
return b.context
}

// WriteTo writes the contents of a BufferList to an io.Writer.
func (b *BufferList) WriteTo(w io.Writer) (n int64, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Were the named return values intentional? Feel free to disregard, just pointing it out since I don't see these names being directly used in the function body.

Copy link
Member Author

Choose a reason for hiding this comment

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

Must have been from a previous iteration of the code. Removed.

@ypatia ypatia requested a review from a team November 27, 2024 08:47
Copy link
Member

@ypatia ypatia left a comment

Choose a reason for hiding this comment

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

Thanks for this very useful optimization, great job! One question, is this Core PR a prerequisite? If yes let's mark this one as Draft so that we don't accidentally merge it before Go has bundled to a Core version that includes that change.

@teo-tsirpanis
Copy link
Member Author

Strictly speaking no, this PR can be merged without waiting for the Core PR. The Core PR is a prerequisite for fully taking advantage of the APIs added in this PR in the REST server (and even this might not be true, I have thought of a potential workaround).

@teo-tsirpanis teo-tsirpanis requested a review from a team November 27, 2024 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants