Skip to content

Commit 05e9d5c

Browse files
authored
Avoid large reallocations when freezing BytesMut (#592)
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.
1 parent f15bba3 commit 05e9d5c

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

src/bytes.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -807,8 +807,36 @@ impl From<&'static str> for Bytes {
807807

808808
impl From<Vec<u8>> for Bytes {
809809
fn from(vec: Vec<u8>) -> Bytes {
810-
let slice = vec.into_boxed_slice();
811-
slice.into()
810+
let mut vec = vec;
811+
let ptr = vec.as_mut_ptr();
812+
let len = vec.len();
813+
let cap = vec.capacity();
814+
815+
// Avoid an extra allocation if possible.
816+
if len == cap {
817+
return Bytes::from(vec.into_boxed_slice());
818+
}
819+
820+
let shared = Box::new(Shared {
821+
buf: ptr,
822+
cap,
823+
ref_cnt: AtomicUsize::new(1),
824+
});
825+
mem::forget(vec);
826+
827+
let shared = Box::into_raw(shared);
828+
// The pointer should be aligned, so this assert should
829+
// always succeed.
830+
debug_assert!(
831+
0 == (shared as usize & KIND_MASK),
832+
"internal: Box<Shared> should have an aligned pointer",
833+
);
834+
Bytes {
835+
ptr,
836+
len,
837+
data: AtomicPtr::new(shared as _),
838+
vtable: &SHARED_VTABLE,
839+
}
812840
}
813841
}
814842

tests/test_bytes.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,3 +1163,48 @@ fn test_bytes_into_vec_promotable_even() {
11631163
assert_eq!(Vec::from(b2), vec[20..]);
11641164
assert_eq!(Vec::from(b1), vec[..20]);
11651165
}
1166+
1167+
#[test]
1168+
fn test_bytes_vec_conversion() {
1169+
let mut vec = Vec::with_capacity(10);
1170+
vec.extend(b"abcdefg");
1171+
let b = Bytes::from(vec);
1172+
let v = Vec::from(b);
1173+
assert_eq!(v.len(), 7);
1174+
assert_eq!(v.capacity(), 10);
1175+
1176+
let mut b = Bytes::from(v);
1177+
b.advance(1);
1178+
let v = Vec::from(b);
1179+
assert_eq!(v.len(), 6);
1180+
assert_eq!(v.capacity(), 10);
1181+
assert_eq!(v.as_slice(), b"bcdefg");
1182+
}
1183+
1184+
#[test]
1185+
fn test_bytes_mut_conversion() {
1186+
let mut b1 = BytesMut::with_capacity(10);
1187+
b1.extend(b"abcdefg");
1188+
let b2 = Bytes::from(b1);
1189+
let v = Vec::from(b2);
1190+
assert_eq!(v.len(), 7);
1191+
assert_eq!(v.capacity(), 10);
1192+
1193+
let mut b = Bytes::from(v);
1194+
b.advance(1);
1195+
let v = Vec::from(b);
1196+
assert_eq!(v.len(), 6);
1197+
assert_eq!(v.capacity(), 10);
1198+
assert_eq!(v.as_slice(), b"bcdefg");
1199+
}
1200+
1201+
#[test]
1202+
fn test_bytes_capacity_len() {
1203+
for cap in 0..100 {
1204+
for len in 0..=cap {
1205+
let mut v = Vec::with_capacity(cap);
1206+
v.resize(len, 0);
1207+
let _ = Bytes::from(v);
1208+
}
1209+
}
1210+
}

0 commit comments

Comments
 (0)