Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 23 additions & 71 deletions benches/image_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ pub struct Time {
pub nsecs: u32,
}

// Basic Image Representation
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct VecImage {
pub header: Header,
pub height: u32,
pub width: u32,
pub encoding: String,
pub is_bigendian: u8,
pub step: u32,
pub data: Vec<u8>,
}

// Includes serde_bytes optimization
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct VecBytesImage {
Expand All @@ -53,84 +41,48 @@ pub struct VecBytesImage {
pub data: Vec<u8>,
}

// Below two options not currently supported
// // No serde_bytes optimization referenced data instead of copying it
// // Note: Deserializer is not really setup to take advantage of this yet
// #[derive(Deserialize, Serialize, PartialEq, Debug)]
// pub struct RefImage<'a> {
// pub header: Header,
// pub height: u32,
// pub width: u32,
// pub encoding: String,
// pub is_bigendian: u8,
// pub step: u32,
// pub data: &'a [u8],
// }

// // With serde_bytes optimization, on referenced data
// #[derive(Deserialize, Serialize, PartialEq, Debug)]
// pub struct RefBytesImage<'a> {
// pub header: Header,
// pub height: u32,
// pub width: u32,
// pub encoding: String,
// pub is_bigendian: u8,
// pub step: u32,
// #[serde(with = "serde_bytes")]
// pub data: &'a [u8],
// }

// An alternate expression option that also works
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SharedImage {
pub header: Header,
pub height: u32,
pub width: u32,
pub encoding: String,
pub is_bigendian: u8,
pub step: u32,
pub data: Box<[u8]>,
}

#[inline]
fn parse_vec_image() {
let image: VecImage = roslibrust_serde_rosmsg::from_slice(IMAGE_DATA).unwrap();
black_box(image);
}

#[inline]
fn parse_vec_bytes_image() {
fn parse_image() {
let image: VecBytesImage = roslibrust_serde_rosmsg::from_slice(IMAGE_DATA).unwrap();
black_box(image);
}

#[inline]
fn parse_shared_image() {
let image: SharedImage = roslibrust_serde_rosmsg::from_slice(IMAGE_DATA).unwrap();
black_box(image);
fn serialize_image_to_new_vec(image: &VecBytesImage) {
black_box(roslibrust_serde_rosmsg::to_vec(image).unwrap());
}

#[inline]
fn serialize_vec_bytes_image(image: &VecBytesImage) {
black_box(roslibrust_serde_rosmsg::to_vec(image).unwrap());
fn serialize_image_to_prealloc_cursor(
image: &VecBytesImage,
cursor: &mut std::io::Cursor<Vec<u8>>,
) {
cursor.set_position(0);
black_box(roslibrust_serde_rosmsg::to_writer(cursor, image).unwrap());
}

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("parse_vec_image", |b| b.iter(|| parse_vec_image()));
c.bench_function("parse_vec_bytes_image", |b| {
b.iter(|| parse_vec_bytes_image())
});
c.bench_function("parse_shared_image", |b| b.iter(|| parse_shared_image()));
c.bench_function("parse_image", |b| b.iter(|| parse_image()));

let image: VecBytesImage = roslibrust_serde_rosmsg::from_slice(IMAGE_DATA).unwrap();
c.bench_function("serialize_vec_bytes_image", |b| {
b.iter(|| serialize_vec_bytes_image(&image))

// Benchmark serialization to a new Vec (allocates on each call)
c.bench_function("serialize_image_to_new_vec", |b| {
b.iter(|| serialize_image_to_new_vec(&image))
});

// Benchmark serialization to a pre-allocated Vec (reuses allocation)
// Pre-allocate a buffer large enough for the serialized image
let serialized_size = roslibrust_serde_rosmsg::to_vec(&image).unwrap().len();
c.bench_function("serialize_image_to_prealloc_cursor", |b| {
let mut cursor = std::io::Cursor::new(Vec::with_capacity(serialized_size));
b.iter(|| serialize_image_to_prealloc_cursor(&image, &mut cursor))
});
}

criterion_group!(
name = benches;
config = Criterion::default().with_profiler(PProfProfiler::new(1000, Output::Flamegraph(None)));
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
targets = criterion_benchmark
);
criterion_main!(benches);
33 changes: 20 additions & 13 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::error::{Error, ErrorKind, Result, ResultExt};
use byteorder::{LittleEndian, ReadBytesExt};
use error_chain::*;
use serde::de;
use std::io;
use std::io::{self, Read};

/// A structure for deserializing ROSMSG into Rust values.
///
Expand Down Expand Up @@ -129,26 +129,33 @@ where
.chain_err(|| ErrorKind::EndOfBuffer)
}

#[inline]
fn read_exact_vec(&mut self, length: u32) -> Result<Vec<u8>> {
let length = length as usize;
let mut buffer = Vec::with_capacity(length);
let read = (&mut self.reader)
.take(length as u64)
.read_to_end(&mut buffer)
.chain_err(|| ErrorKind::EndOfBuffer)?;
if read != length {
bail!(ErrorKind::EndOfBuffer);
}
Ok(buffer)
}

#[inline]
fn get_string(&mut self) -> Result<String> {
let length = self.pop_length()?;
self.reserve_bytes(length)?;
let mut buffer = vec![0; length as usize];
self.reader
.read_exact(&mut buffer)
.chain_err(|| ErrorKind::EndOfBuffer)?;
let buffer = self.read_exact_vec(length)?;
String::from_utf8(buffer).chain_err(|| ErrorKind::BadStringData)
}

#[inline]
fn get_byte_buf(&mut self) -> Result<Vec<u8>> {
let length = self.pop_length()?;
let mut buffer = vec![0; length as usize];
self.reader
.read_exact(&mut buffer)
.chain_err(|| ErrorKind::EndOfBuffer)?;
self.length -= length;
Ok(buffer)
self.reserve_bytes(length)?;
self.read_exact_vec(length)
}
}

Expand Down Expand Up @@ -599,7 +606,7 @@ pub fn from_slice<'de, T>(bytes: &[u8]) -> Result<T>
where
T: de::Deserialize<'de>,
{
from_reader(io::Cursor::new(bytes))
from_reader(bytes)
}

/// Variant of [from_slice] where the 4 bytes for the overall message length
Expand All @@ -608,7 +615,7 @@ pub fn from_slice_known_length<'de, T>(bytes: &[u8], length: u32) -> Result<T>
where
T: de::Deserialize<'de>,
{
from_reader_known_length(io::Cursor::new(bytes), length)
from_reader_known_length(bytes, length)
}

/// Deserialize an instance of type `T` from a string of ROSMSG data.
Expand Down
73 changes: 64 additions & 9 deletions src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,12 +421,15 @@ impl ser::Error for Error {
}
}

/// Serialize the given data structure `T` as ROSMSG into the IO stream.
/// Serialize the given data structure `T` as ROSMSG into a seekable IO stream.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// fail. It can also fail if the structure contains unsupported elements.
///
/// Finally, it can also fail due to writer failure.
/// Finally, it can also fail due to writer or seek failure.
///
/// This function writes a placeholder length, serializes the data directly to the writer,
/// then seeks back to write the correct length. This avoids intermediate buffer allocation.
///
/// # Examples
///
Expand All @@ -439,15 +442,31 @@ impl ser::Error for Error {
/// ```
pub fn to_writer<W, T>(writer: &mut W, value: &T) -> Result<()>
where
W: io::Write,
W: io::Write + io::Seek,
T: ser::Serialize,
{
let mut buffer = Vec::new();
value.serialize(&mut Serializer::new(&mut buffer))?;
writer
.write_u32::<LittleEndian>(buffer.len() as u32)
.and_then(|_| writer.write_all(&buffer))
.map_err(|v| v.into())
// Write placeholder for length (will be overwritten)
let start_pos = writer.stream_position()?;
writer.write_u32::<LittleEndian>(0)?;

// Serialize the value
let data_start = writer.stream_position()?;
let mut serializer = Serializer::new(writer);
value.serialize(&mut serializer)?;
let writer = serializer.into_inner();
let end_pos = writer.stream_position()?;

// Calculate the data length
let length = (end_pos - data_start) as u32;

// Seek back and write the actual length
writer.seek(io::SeekFrom::Start(start_pos))?;
writer.write_u32::<LittleEndian>(length)?;

// Seek back to end
writer.seek(io::SeekFrom::Start(end_pos))?;

Ok(())
}

/// Variant of [to_writer] where the 4 bytes for the overall message length are skipped
Expand Down Expand Up @@ -739,4 +758,40 @@ mod tests {
] == answer
);
}

#[test]
fn to_writer_basic() {
let mut cursor = io::Cursor::new(Vec::new());
to_writer(&mut cursor, &String::from("Hello, World!")).unwrap();
assert_eq!(cursor.into_inner(), b"\x11\0\0\0\x0d\0\0\0Hello, World!");
}

#[test]
fn to_writer_same_as_to_vec() {
let test_value = vec![1u32, 2u32, 3u32, 4u32, 5u32];

let expected = to_vec(&test_value).unwrap();

let mut cursor = io::Cursor::new(Vec::new());
to_writer(&mut cursor, &test_value).unwrap();

assert_eq!(cursor.into_inner(), expected);
}

#[test]
fn to_writer_multiple_times() {
let mut cursor = io::Cursor::new(Vec::new());

// First write
to_writer(&mut cursor, &String::from("Hello")).unwrap();

// Second write appends
to_writer(&mut cursor, &String::from("World")).unwrap();

let result = cursor.into_inner();

// Should contain both messages
assert_eq!(&result[0..13], b"\x09\0\0\0\x05\0\0\0Hello");
assert_eq!(&result[13..26], b"\x09\0\0\0\x05\0\0\0World");
}
}
Loading