From e01f960ddcd45a0cdf86c737d04737d217ad384a Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 16 Jun 2020 17:46:55 +0200 Subject: [PATCH] Benchmarks and test factorization --- implementations/rust/Cargo.toml | 7 ++ implementations/rust/Makefile | 5 +- implementations/rust/benches/codec.rs | 67 +++++++++++++++ implementations/rust/src/lib.rs | 94 --------------------- implementations/rust/src/value/reader.rs | 5 +- implementations/rust/tests/samples/mod.rs | 21 +++++ implementations/rust/tests/samples_tests.rs | 74 ++++++++++++++++ 7 files changed, 177 insertions(+), 96 deletions(-) create mode 100644 implementations/rust/benches/codec.rs create mode 100644 implementations/rust/tests/samples/mod.rs create mode 100644 implementations/rust/tests/samples_tests.rs diff --git a/implementations/rust/Cargo.toml b/implementations/rust/Cargo.toml index 50fe818..2d6ac47 100644 --- a/implementations/rust/Cargo.toml +++ b/implementations/rust/Cargo.toml @@ -17,3 +17,10 @@ num_enum = "0.4.1" serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11" lazy_static = "1.4.0" + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "codec" +harness = false diff --git a/implementations/rust/Makefile b/implementations/rust/Makefile index 55ae66e..3e49826 100644 --- a/implementations/rust/Makefile +++ b/implementations/rust/Makefile @@ -1,6 +1,9 @@ # cargo install cargo-watch watch: - cargo watch -c -x 'check --all-targets' -x 'test --all-targets -- --nocapture' + cargo watch -c -x 'test --all-targets -- --nocapture' + +bench: + cargo bench --benches clippy-watch: cargo watch -c -x 'clippy --all-targets' diff --git a/implementations/rust/benches/codec.rs b/implementations/rust/benches/codec.rs new file mode 100644 index 0000000..eceb905 --- /dev/null +++ b/implementations/rust/benches/codec.rs @@ -0,0 +1,67 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use preserves::value::{self, decoder, encoder}; +use preserves::{de, ser}; +use std::io::Read; + +#[path = "../tests/samples/mod.rs"] +mod samples; +use samples::TestCases; + +pub fn bench_decoder(c: &mut Criterion) { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let mut bs = vec![]; + fh.read_to_end(&mut bs).ok(); + c.bench_function("decode samples.bin", |b| b.iter( + || decoder::from_bytes(&bs[..]).demand_next().unwrap())); +} + +pub fn bench_encoder(c: &mut Criterion) { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let v = decoder::from_read(&mut fh).demand_next().unwrap(); + c.bench_function("encode samples.bin", |b| b.iter(|| { + let mut bs = vec![]; + encoder::Encoder::new(&mut bs).write(&v).unwrap(); + bs + })); +} + +pub fn bench_de(c: &mut Criterion) { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let mut bs = vec![]; + fh.read_to_end(&mut bs).ok(); + c.bench_function("deserialize samples.bin", |b| b.iter( + || de::from_bytes::(&bs[..]).unwrap())); +} + +pub fn bench_ser(c: &mut Criterion) { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let v: TestCases = de::from_read(&mut fh).unwrap(); + c.bench_function("serialize samples.bin", |b| b.iter(|| { + let mut bs = vec![]; + ser::to_writer(&mut bs, &v).unwrap(); + bs + })); +} + +pub fn bench_decoder_de(c: &mut Criterion) { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let mut bs = vec![]; + fh.read_to_end(&mut bs).ok(); + c.bench_function("decode-then-deserialize samples.bin", |b| b.iter( + || value::de::from_value::(&decoder::from_bytes(&bs[..]).demand_next().unwrap()).unwrap())); +} + +pub fn bench_ser_encoder(c: &mut Criterion) { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let v: TestCases = de::from_read(&mut fh).unwrap(); + c.bench_function("serialize-then-encode samples.bin", |b| b.iter(|| { + let mut bs = vec![]; + encoder::Encoder::new(&mut bs).write(&value::ser::to_value(&v)).unwrap(); + bs + })); +} + +criterion_group!(codec, bench_decoder, bench_encoder); +criterion_group!(serde, bench_de, bench_ser); +criterion_group!(codec_then_serde, bench_decoder_de, bench_ser_encoder); +criterion_main!(codec, serde, codec_then_serde); diff --git a/implementations/rust/src/lib.rs b/implementations/rust/src/lib.rs index 5aeb109..3f5b70f 100644 --- a/implementations/rust/src/lib.rs +++ b/implementations/rust/src/lib.rs @@ -489,97 +489,3 @@ mod serde_tests { assert_eq!(v, y); } } - -#[cfg(test)] -mod samples_tests { - use crate::symbol::Symbol; - use crate::error::{is_eof_io_error, is_syntax_io_error}; - use crate::value::de::from_value as deserialize_from_value; - use crate::value::decoder; - use crate::value::encoder::encode_bytes; - use crate::value::{IOValue, Map}; - use std::iter::Iterator; - - #[derive(Debug, serde::Serialize, serde::Deserialize)] - struct TestCases { - tests: Map - } - - #[derive(Debug, serde::Serialize, serde::Deserialize)] - enum TestCase { - Test(#[serde(with = "serde_bytes")] Vec, IOValue), - NondeterministicTest(#[serde(with = "serde_bytes")] Vec, IOValue), - StreamingTest(#[serde(with = "serde_bytes")] Vec, IOValue), - DecodeTest(#[serde(with = "serde_bytes")] Vec, IOValue), - ParseError(String), - ParseShort(String), - ParseEOF(String), - DecodeError(#[serde(with = "serde_bytes")] Vec), - DecodeShort(#[serde(with = "serde_bytes")] Vec), - DecodeEOF(#[serde(with = "serde_bytes")] Vec), - } - - fn decode_all<'de>(bytes: &'de [u8]) -> Result, std::io::Error> { - let d = decoder::from_bytes(bytes); - d.collect() - } - - #[test] fn run() -> std::io::Result<()> { - let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); - let mut d = decoder::from_read(&mut fh); - let tests: TestCases = deserialize_from_value(&d.next().unwrap().unwrap()).unwrap(); - // println!("{:#?}", tests); - - for (Symbol(ref name), ref case) in tests.tests { - println!("{:?} ==> {:?}", name, case); - match case { - TestCase::Test(ref bin, ref val) => { - assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); - assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); - assert_eq!(&encode_bytes(val)?, bin); - } - TestCase::NondeterministicTest(ref bin, ref val) => { - // The test cases in samples.txt are carefully - // written so that while strictly - // "nondeterministic", the order of keys in - // dictionaries follows Preserves order. - assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); - assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); - assert_eq!(&encode_bytes(val)?, bin); - } - TestCase::StreamingTest(ref bin, ref val) => { - assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); - assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); - } - TestCase::DecodeTest(ref bin, ref val) => { - assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); - assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); - } - TestCase::ParseError(_) => (), - TestCase::ParseShort(_) => (), - TestCase::ParseEOF(_) => (), - TestCase::DecodeError(ref bin) => { - match decode_all(&bin[..]) { - Ok(_) => panic!("Unexpected success"), - Err(e) => if is_syntax_io_error(&e) { - () - } else { - panic!("Unexpected error {:?}", e) - } - } - } - TestCase::DecodeShort(ref bin) => { - assert!(if let Err(e) = decoder::from_bytes(bin).next().unwrap() { - is_eof_io_error(&e) - } else { - false - }) - } - TestCase::DecodeEOF(ref bin) => { - assert!(decoder::from_bytes(bin).next().is_none()); - } - } - } - Ok(()) - } -} diff --git a/implementations/rust/src/value/reader.rs b/implementations/rust/src/value/reader.rs index 67acfc9..9b547ff 100644 --- a/implementations/rust/src/value/reader.rs +++ b/implementations/rust/src/value/reader.rs @@ -426,7 +426,10 @@ impl<'de, S: BinarySource<'de>> BinaryReader<'de, S> { fn peek_next_nonannotation_op(&mut self) -> ReaderResult<(Op, u8)> { loop { match decodeop(self.peek()?)? { - (Op::Misc(0), 5) => self.skip()?, + (Op::Misc(0), 5) => { + self.skip()?; + self.skip_value()?; + }, other => return Ok(other), } } diff --git a/implementations/rust/tests/samples/mod.rs b/implementations/rust/tests/samples/mod.rs new file mode 100644 index 0000000..9272431 --- /dev/null +++ b/implementations/rust/tests/samples/mod.rs @@ -0,0 +1,21 @@ +use preserves::symbol::Symbol; +use preserves::value::{IOValue, Map}; + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TestCases { + pub tests: Map +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub enum TestCase { + Test(#[serde(with = "serde_bytes")] Vec, IOValue), + NondeterministicTest(#[serde(with = "serde_bytes")] Vec, IOValue), + StreamingTest(#[serde(with = "serde_bytes")] Vec, IOValue), + DecodeTest(#[serde(with = "serde_bytes")] Vec, IOValue), + ParseError(String), + ParseShort(String), + ParseEOF(String), + DecodeError(#[serde(with = "serde_bytes")] Vec), + DecodeShort(#[serde(with = "serde_bytes")] Vec), + DecodeEOF(#[serde(with = "serde_bytes")] Vec), +} diff --git a/implementations/rust/tests/samples_tests.rs b/implementations/rust/tests/samples_tests.rs new file mode 100644 index 0000000..dfca6c4 --- /dev/null +++ b/implementations/rust/tests/samples_tests.rs @@ -0,0 +1,74 @@ +use preserves::error::{is_eof_io_error, is_syntax_io_error}; +use preserves::symbol::Symbol; +use preserves::value::de::from_value as deserialize_from_value; +use preserves::value::decoder; +use preserves::value::encoder::encode_bytes; +use preserves::value::IOValue; +use std::iter::Iterator; + +mod samples; +use samples::*; + +fn decode_all<'de>(bytes: &'de [u8]) -> Result, std::io::Error> { + let d = decoder::from_bytes(bytes); + d.collect() +} + +#[test] fn run() -> std::io::Result<()> { + let mut fh = std::fs::File::open("../../tests/samples.bin").unwrap(); + let mut d = decoder::from_read(&mut fh); + let tests: TestCases = deserialize_from_value(&d.next().unwrap().unwrap()).unwrap(); + // println!("{:#?}", tests); + + for (Symbol(ref name), ref case) in tests.tests { + println!("{:?} ==> {:?}", name, case); + match case { + TestCase::Test(ref bin, ref val) => { + assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); + assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); + assert_eq!(&encode_bytes(val)?, bin); + } + TestCase::NondeterministicTest(ref bin, ref val) => { + // The test cases in samples.txt are carefully + // written so that while strictly + // "nondeterministic", the order of keys in + // dictionaries follows Preserves order. + assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); + assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); + assert_eq!(&encode_bytes(val)?, bin); + } + TestCase::StreamingTest(ref bin, ref val) => { + assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); + assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); + } + TestCase::DecodeTest(ref bin, ref val) => { + assert_eq!(&decode_all(&encode_bytes(val)?[..])?, &[val.clone()]); + assert_eq!(&decode_all(&bin[..])?, &[val.clone()]); + } + TestCase::ParseError(_) => (), + TestCase::ParseShort(_) => (), + TestCase::ParseEOF(_) => (), + TestCase::DecodeError(ref bin) => { + match decode_all(&bin[..]) { + Ok(_) => panic!("Unexpected success"), + Err(e) => if is_syntax_io_error(&e) { + () + } else { + panic!("Unexpected error {:?}", e) + } + } + } + TestCase::DecodeShort(ref bin) => { + assert!(if let Err(e) = decoder::from_bytes(bin).next().unwrap() { + is_eof_io_error(&e) + } else { + false + }) + } + TestCase::DecodeEOF(ref bin) => { + assert!(decoder::from_bytes(bin).next().is_none()); + } + } + } + Ok(()) +}