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

Avoid large reallocations when freezing BytesMut #592

Merged
merged 1 commit into from Jan 31, 2023

Conversation

bk2204
Copy link
Contributor

@bk2204 bk2204 commented Jan 29, 2023

When we freeze a BytesMut, we turn it into a Vec, and then convert that to a Bytes. Currently, this happen using Vec::into_boxed_slice, which reallocates to a slice of the same length as the Vev if the length and the capacity are not equal. This can pose a performance problem if the Vec is large or if this happens many times in a loop.

Instead, let's compare the length and capacity, and if they're the same, continue to handle this using into_boxed_slice. Otherwise, since we have a type of vtable which can handle a separate capacity, the shared vtable, let's turn our Vec into that kind of Bytes. While this does not avoid allocation altogether, it performs a fixed size allocation and avoids any need to memcpy.

Closes: #587

@bk2204 bk2204 force-pushed the from-vec-allocation branch 2 times, most recently from 403b7ca to ea5b280 Compare January 30, 2023 18:28
@bk2204
Copy link
Contributor Author

bk2204 commented Jan 30, 2023

I believe I've fixed the test failure here, but will need to wait for CI to verify.

src/bytes.rs Outdated Show resolved Hide resolved
@bk2204 bk2204 force-pushed the from-vec-allocation branch 3 times, most recently from 0a71a88 to 492ea55 Compare January 31, 2023 10:03
@bk2204
Copy link
Contributor Author

bk2204 commented Jan 31, 2023

I've changed from using core::mem::forget to simply mem::forget, and assuming this passes CI, I think it should be ready for a final review.

Copy link
Contributor

@Darksonn Darksonn left a comment

Choose a reason for hiding this comment

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

I would like to see a miri test long these lines:

for cap in 0..100 {
    for len in 0..cap {
        let mut vec = Vec::with_capacity(cap);
        vec.resize(len, 0);
        let _ = Bytes::from(vec);
    }
}

When we freeze a BytesMut, we turn it into a Vec, and then convert that
to a Bytes.  Currently, this happen using Vec::into_boxed_slice, which
reallocates to a slice of the same length as the Vev if the length and
the capacity are not equal.  This can pose a performance problem if the
Vec is large or if this happens many times in a loop.

Instead, let's compare the length and capacity, and if they're the same,
continue to handle this using into_boxed_slice.  Otherwise, since we
have a type of vtable which can handle a separate capacity, the shared
vtable, let's turn our Vec into that kind of Bytes.  While this does not
avoid allocation altogether, it performs a fixed size allocation and
avoids any need to memcpy.
@bk2204
Copy link
Contributor Author

bk2204 commented Jan 31, 2023

Sure, I've added such a test. I took the liberty of also checking a length equal to the size of the capacity because I think that makes sense to test as well.

Copy link
Contributor

@Darksonn Darksonn left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@Darksonn Darksonn merged commit 05e9d5c into tokio-rs:master Jan 31, 2023
@bk2204 bk2204 deleted the from-vec-allocation branch January 31, 2023 19:06
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.

BytesMut::freeze is not always zero-cost as documented
3 participants