-
-
Notifications
You must be signed in to change notification settings - Fork 277
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
Improved encoding and decoding speed of Vec<u8> #619
Changes from all commits
4a01495
20c0939
54769cf
e613898
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// https://github.com/bincode-org/bincode/issues/618 | ||
|
||
use bincode::{Decode, Encode}; | ||
use criterion::{black_box, criterion_group, criterion_main, Criterion}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Serialize, Deserialize, Default, Encode, Decode)] | ||
pub struct MyStruct { | ||
pub v: Vec<String>, | ||
pub string: String, | ||
pub number: usize, | ||
} | ||
|
||
impl MyStruct { | ||
#[inline] | ||
pub fn new(v: Vec<String>, string: String, number: usize) -> Self { | ||
Self { v, string, number } | ||
} | ||
} | ||
|
||
fn build_data(size: usize) -> Vec<MyStruct> { | ||
(0..size) | ||
.map(|i| { | ||
let vec: Vec<String> = (0..i).map(|i| i.to_string().repeat(100)).collect(); | ||
MyStruct::new(vec, size.to_string(), size) | ||
}) | ||
.collect() | ||
} | ||
|
||
fn index_item_decode(c: &mut Criterion) { | ||
let data = build_data(100); | ||
|
||
c.bench_function("bench v1", |b| { | ||
b.iter(|| { | ||
let _ = black_box(bincode_1::serialize(black_box(&data))).unwrap(); | ||
}); | ||
}); | ||
|
||
let config = bincode::config::standard(); | ||
c.bench_function("bench v2 (standard)", |b| { | ||
b.iter(|| { | ||
let _ = black_box(bincode::encode_to_vec(black_box(&data), config)).unwrap(); | ||
}); | ||
}); | ||
|
||
let config = bincode::config::legacy(); | ||
c.bench_function("bench v2 (legacy)", |b| { | ||
b.iter(|| { | ||
let _ = black_box(bincode::encode_to_vec(black_box(&data), config)).unwrap(); | ||
}); | ||
}); | ||
|
||
let encodedv1 = bincode_1::serialize(&data).unwrap(); | ||
let encodedv2 = bincode::encode_to_vec(&data, config).unwrap(); | ||
assert_eq!(encodedv1, encodedv2); | ||
|
||
c.bench_function("bench v1 decode", |b| { | ||
b.iter(|| { | ||
let _: Vec<MyStruct> = | ||
black_box(bincode_1::deserialize(black_box(&encodedv1))).unwrap(); | ||
}); | ||
}); | ||
|
||
c.bench_function("bench v2 decode (legacy)", |b| { | ||
b.iter(|| { | ||
let _: (Vec<MyStruct>, _) = | ||
black_box(bincode::decode_from_slice(black_box(&encodedv1), config)).unwrap(); | ||
}); | ||
}); | ||
} | ||
|
||
criterion_group!(benches, index_item_decode); | ||
criterion_main!(benches); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
use crate::{ | ||
de::{BorrowDecoder, Decode, Decoder}, | ||
enc::{self, Encode, Encoder}, | ||
de::{read::Reader, BorrowDecoder, Decode, Decoder}, | ||
enc::{ | ||
self, | ||
write::{SizeWriter, Writer}, | ||
Encode, Encoder, | ||
}, | ||
error::{DecodeError, EncodeError}, | ||
impl_borrow_decode, BorrowDecode, Config, | ||
}; | ||
|
@@ -21,6 +25,12 @@ pub(crate) struct VecWriter { | |
} | ||
|
||
impl VecWriter { | ||
/// Create a new vec writer with the given capacity | ||
pub fn with_capacity(cap: usize) -> Self { | ||
Self { | ||
inner: Vec::with_capacity(cap), | ||
} | ||
} | ||
// May not be used in all feature combinations | ||
#[allow(dead_code)] | ||
pub(crate) fn collect(self) -> Vec<u8> { | ||
|
@@ -29,6 +39,7 @@ impl VecWriter { | |
} | ||
|
||
impl enc::write::Writer for VecWriter { | ||
#[inline(always)] | ||
fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> { | ||
self.inner.extend_from_slice(bytes); | ||
Ok(()) | ||
|
@@ -40,7 +51,12 @@ impl enc::write::Writer for VecWriter { | |
/// [config]: config/index.html | ||
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] | ||
pub fn encode_to_vec<E: enc::Encode, C: Config>(val: E, config: C) -> Result<Vec<u8>, EncodeError> { | ||
let writer = VecWriter::default(); | ||
let size = { | ||
let mut size_writer = enc::EncoderImpl::<_, C>::new(SizeWriter::default(), config); | ||
val.encode(&mut size_writer)?; | ||
size_writer.into_writer().bytes_written | ||
}; | ||
let writer = VecWriter::with_capacity(size); | ||
let mut encoder = enc::EncoderImpl::<_, C>::new(writer, config); | ||
val.encode(&mut encoder)?; | ||
Ok(encoder.into_writer().inner) | ||
|
@@ -262,10 +278,20 @@ where | |
|
||
impl<T> Decode for Vec<T> | ||
where | ||
T: Decode, | ||
T: Decode + 'static, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this bound rule out zero-copy deserialization of types like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To answer my own question: No, as this case goes though |
||
{ | ||
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> { | ||
let len = crate::de::decode_slice_len(decoder)?; | ||
|
||
if core::any::TypeId::of::<T>() == core::any::TypeId::of::<u8>() { | ||
decoder.claim_container_read::<T>(len)?; | ||
// optimize for reading u8 vecs | ||
let mut vec = Vec::new(); | ||
vec.resize(len, 0u8); | ||
decoder.reader().read(&mut vec)?; | ||
// Safety: Vec<T> is Vec<u8> | ||
return Ok(unsafe { core::mem::transmute(vec) }); | ||
} | ||
decoder.claim_container_read::<T>(len)?; | ||
|
||
let mut vec = Vec::with_capacity(len); | ||
|
@@ -300,10 +326,15 @@ where | |
|
||
impl<T> Encode for Vec<T> | ||
where | ||
T: Encode, | ||
T: Encode + 'static, | ||
{ | ||
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> { | ||
crate::enc::encode_slice_len(encoder, self.len())?; | ||
if core::any::TypeId::of::<T>() == core::any::TypeId::of::<u8>() { | ||
let slice: &[u8] = unsafe { core::mem::transmute(self.as_slice()) }; | ||
encoder.writer().write(slice)?; | ||
return Ok(()); | ||
} | ||
for item in self.iter() { | ||
item.encode(encoder)?; | ||
} | ||
|
@@ -364,7 +395,7 @@ where | |
|
||
impl<T> Decode for Box<[T]> | ||
where | ||
T: Decode, | ||
T: Decode + 'static, | ||
{ | ||
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> { | ||
let vec = Vec::decode(decoder)?; | ||
|
@@ -444,7 +475,7 @@ where | |
|
||
impl<T> Decode for Rc<[T]> | ||
where | ||
T: Decode, | ||
T: Decode + 'static, | ||
{ | ||
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> { | ||
let vec = Vec::decode(decoder)?; | ||
|
@@ -513,7 +544,7 @@ where | |
#[cfg(target_has_atomic = "ptr")] | ||
impl<T> Decode for Arc<[T]> | ||
where | ||
T: Decode, | ||
T: Decode + 'static, | ||
{ | ||
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> { | ||
let vec = Vec::decode(decoder)?; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change now gives me errors when trying to derive
Encode
for types withVec
s that contain references.For example:
Argument requires that 'a must outlive 'static
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted this PR in #663 and tried a better fix in #667