diff --git a/Cargo.toml b/Cargo.toml index 0339e0ed..9adabe75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,8 @@ rand = "0.8" uuid = { version = "1.1", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] } glam = { version = "0.21", features = ["serde"] } +bincode_1 = { version = "1.3", package = "bincode" } +serde = { version = "1.0", features = ["derive"] } [[bench]] name = "varint" @@ -53,9 +55,14 @@ harness = false name = "inline" harness = false +[[bench]] +name = "string" +harness = false + [profile.bench] codegen-units = 1 debug = 1 +lto = true [package.metadata.docs.rs] all-features = true diff --git a/benches/string.rs b/benches/string.rs new file mode 100644 index 00000000..f565b4af --- /dev/null +++ b/benches/string.rs @@ -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, + pub string: String, + pub number: usize, +} + +impl MyStruct { + #[inline] + pub fn new(v: Vec, string: String, number: usize) -> Self { + Self { v, string, number } + } +} + +fn build_data(size: usize) -> Vec { + (0..size) + .map(|i| { + let vec: Vec = (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 = + black_box(bincode_1::deserialize(black_box(&encodedv1))).unwrap(); + }); + }); + + c.bench_function("bench v2 decode (legacy)", |b| { + b.iter(|| { + let _: (Vec, _) = + black_box(bincode::decode_from_slice(black_box(&encodedv1), config)).unwrap(); + }); + }); +} + +criterion_group!(benches, index_item_decode); +criterion_main!(benches); diff --git a/src/enc/encoder.rs b/src/enc/encoder.rs index 16f050bc..564da0bb 100644 --- a/src/enc/encoder.rs +++ b/src/enc/encoder.rs @@ -32,6 +32,7 @@ impl EncoderImpl { } /// Return the underlying writer + #[inline] pub fn into_writer(self) -> W { self.writer } @@ -42,10 +43,12 @@ impl Encoder for EncoderImpl { type C = C; + #[inline] fn writer(&mut self) -> &mut Self::W { &mut self.writer } + #[inline] fn config(&self) -> &Self::C { &self.config } diff --git a/src/enc/impls.rs b/src/enc/impls.rs index c715ccab..4c8df9ec 100644 --- a/src/enc/impls.rs +++ b/src/enc/impls.rs @@ -295,10 +295,17 @@ impl Encode for char { impl Encode for [T] where - T: Encode, + T: Encode + 'static, { fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { super::encode_slice_len(encoder, self.len())?; + + if core::any::TypeId::of::() == core::any::TypeId::of::() { + let t: &[u8] = unsafe { core::mem::transmute(self) }; + encoder.writer().write(t)?; + return Ok(()); + } + for item in self { item.encode(encoder)?; } diff --git a/src/enc/write.rs b/src/enc/write.rs index 0575dc75..412cc459 100644 --- a/src/enc/write.rs +++ b/src/enc/write.rs @@ -65,3 +65,18 @@ impl<'storage> Writer for SliceWriter<'storage> { Ok(()) } } + +/// A writer that counts how many bytes were written. This is useful for e.g. pre-allocating buffers bfeore writing to them. +#[derive(Default)] +pub struct SizeWriter { + /// the amount of bytes that were written so far + pub bytes_written: usize, +} +impl Writer for SizeWriter { + #[inline(always)] + fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> { + self.bytes_written += bytes.len(); + + Ok(()) + } +} diff --git a/src/features/impl_alloc.rs b/src/features/impl_alloc.rs index b705de96..0ba11b0f 100644 --- a/src/features/impl_alloc.rs +++ b/src/features/impl_alloc.rs @@ -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 { @@ -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(val: E, config: C) -> Result, 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 Decode for Vec where - T: Decode, + T: Decode + 'static, { fn decode(decoder: &mut D) -> Result { let len = crate::de::decode_slice_len(decoder)?; + + if core::any::TypeId::of::() == core::any::TypeId::of::() { + decoder.claim_container_read::(len)?; + // optimize for reading u8 vecs + let mut vec = Vec::new(); + vec.resize(len, 0u8); + decoder.reader().read(&mut vec)?; + // Safety: Vec is Vec + return Ok(unsafe { core::mem::transmute(vec) }); + } decoder.claim_container_read::(len)?; let mut vec = Vec::with_capacity(len); @@ -300,10 +326,15 @@ where impl Encode for Vec where - T: Encode, + T: Encode + 'static, { fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { crate::enc::encode_slice_len(encoder, self.len())?; + if core::any::TypeId::of::() == core::any::TypeId::of::() { + 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 Decode for Box<[T]> where - T: Decode, + T: Decode + 'static, { fn decode(decoder: &mut D) -> Result { let vec = Vec::decode(decoder)?; @@ -444,7 +475,7 @@ where impl Decode for Rc<[T]> where - T: Decode, + T: Decode + 'static, { fn decode(decoder: &mut D) -> Result { let vec = Vec::decode(decoder)?; @@ -513,7 +544,7 @@ where #[cfg(target_has_atomic = "ptr")] impl Decode for Arc<[T]> where - T: Decode, + T: Decode + 'static, { fn decode(decoder: &mut D) -> Result { let vec = Vec::decode(decoder)?;