diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 498dbc8..2734a02 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" assert_unordered = "0.3.5" structdiff = { path = "..", features = ["serde", "debug_diffs"] } nanorand = { version = "0.7.0" } -diff-struct = { version = "0.5.1", optional = true} +diff-struct = { version = "0.5.3", optional = true} serde = { version = "^1.0.0", features = ["derive"] } serde-diff = { version = "0.4.1", optional = true} bincode = { version = "1.3.3" } @@ -19,6 +19,11 @@ criterion = "0.5.1" default = ["compare"] compare = ["dep:serde-diff", "dep:diff-struct"] +[profile.release] +lto = "fat" +opt-level = 3 +debug = true + [profile.bench] lto = "fat" opt-level = 3 @@ -31,3 +36,8 @@ harness = false [[bench]] name = "large" harness = false + +[[bench]] +name = "basic_mutate" +harness = false + diff --git a/benchmarks/benches/basic_mutate.rs b/benchmarks/benches/basic_mutate.rs new file mode 100644 index 0000000..ad6556a --- /dev/null +++ b/benchmarks/benches/basic_mutate.rs @@ -0,0 +1,124 @@ +#![cfg(test)] + +extern crate structdiff_benchmarks; + +use std::time::Duration; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use nanorand::WyRand; +use structdiff::StructDiff; +use structdiff_benchmarks::TestBench; +const SAMPLE_SIZE: usize = 1000; +const MEASUREMENT_TIME: Duration = Duration::from_secs(25); +const SEED: u64 = 42; + +#[cfg(feature = "compare")] +criterion_group!( + benches, + bench_basic_mutate_generation, + bench_basic_mutate_full, + diff_struct_bench::bench_basic_mutate, + serde_diff_bench::bench_basic_mutate +); +#[cfg(not(feature = "compare"))] +criterion_group!( + benches, + bench_basic_mutate_generation, + bench_basic_mutate_full +); + +criterion_main!(benches); + +fn bench_basic_mutate_generation(c: &mut Criterion) { + const GROUP_NAME: &str = "bench_basic_mutate_ref_gen"; + let mut rng = WyRand::new_seed(SEED); + let first = black_box(TestBench::generate_random(&mut rng)); + let second = black_box(first.clone().random_mutate(&mut rng)); + let mut group = c.benchmark_group(GROUP_NAME); + group + .sample_size(SAMPLE_SIZE) + .measurement_time(MEASUREMENT_TIME); + group.bench_function(GROUP_NAME, |b| { + b.iter(|| { + black_box(StructDiff::diff_ref(&first, &second)); + }) + }); + group.finish(); +} + +fn bench_basic_mutate_full(c: &mut Criterion) { + const GROUP_NAME: &str = "bench_basic_mutate_owned"; + let mut rng = WyRand::new_seed(SEED); + let mut first = black_box(TestBench::generate_random(&mut rng)); + let second = black_box(first.clone().random_mutate(&mut rng)); + let mut group = c.benchmark_group(GROUP_NAME); + group + .sample_size(SAMPLE_SIZE) + .measurement_time(MEASUREMENT_TIME); + group.bench_function(GROUP_NAME, |b| { + b.iter(|| { + let diff = black_box(StructDiff::diff(&first, &second)); + black_box(first.apply_mut(diff)); + }) + }); + group.finish(); +} + +#[cfg(feature = "compare")] +mod diff_struct_bench { + use super::{black_box, Criterion, TestBench, WyRand, MEASUREMENT_TIME, SAMPLE_SIZE, SEED}; + use diff::Diff; + + pub(super) fn bench_basic_mutate(c: &mut Criterion) { + const GROUP_NAME: &str = "diff_struct_bench_basic_mutate"; + let mut rng = WyRand::new_seed(SEED); + let mut first = black_box(TestBench::generate_random(&mut rng)); + let second = black_box(first.clone().random_mutate(&mut rng)); + let mut group = c.benchmark_group(GROUP_NAME); + group + .sample_size(SAMPLE_SIZE) + .measurement_time(MEASUREMENT_TIME); + group.bench_function(GROUP_NAME, |b| { + b.iter(|| { + let diff = black_box(Diff::diff(&first, &second)); + black_box(Diff::apply(&mut first, &diff)) + }) + }); + group.finish(); + assert_eq!(first.b, second.b); + } +} + +#[cfg(feature = "compare")] +mod serde_diff_bench { + use super::{black_box, Criterion, TestBench, WyRand, MEASUREMENT_TIME, SAMPLE_SIZE, SEED}; + use bincode::Options; + + pub(super) fn bench_basic_mutate(c: &mut Criterion) { + const GROUP_NAME: &str = "serde_diff_bench_basic_mutate"; + let mut rng = WyRand::new_seed(SEED); + let mut first = black_box(TestBench::generate_random(&mut rng)); + let second = black_box(first.clone().random_mutate(&mut rng)); + let options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes(); + let mut group = c.benchmark_group(GROUP_NAME); + group + .sample_size(SAMPLE_SIZE) + .measurement_time(MEASUREMENT_TIME); + group.bench_function(GROUP_NAME, |b| { + b.iter(|| { + let mut diff = black_box( + options + .serialize(&serde_diff::Diff::serializable(&first, &second)) + .unwrap(), + ); + let mut deserializer = + black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); + serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); + }) + }); + group.finish(); + assert_eq!(first.b, second.b); + } +} diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs index 597745d..170a821 100644 --- a/benchmarks/src/lib.rs +++ b/benchmarks/src/lib.rs @@ -6,6 +6,9 @@ use structdiff::{Difference, StructDiff}; #[derive(Debug, Difference, PartialEq, Clone, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "compare", derive(diff::Diff))] +#[cfg_attr(feature = "compare", diff(attr( + #[derive(Debug, serde::Serialize, serde::Deserialize)] +)))] #[cfg_attr(feature = "compare", derive(serde_diff::SerdeDiff))] pub struct TestBench { pub a: String, @@ -13,7 +16,7 @@ pub struct TestBench { #[difference(collection_strategy = "unordered_array_like")] #[cfg_attr(feature = "compare", serde_diff(opaque))] pub c: HashSet, - #[difference(collection_strategy = "unordered_array_like")] + #[difference(collection_strategy = "ordered_array_like")] pub d: Vec, #[difference(collection_strategy = "unordered_map_like", map_equality = "key_only")] pub e: HashMap, @@ -25,7 +28,7 @@ pub struct TestBench { } fn rand_string(rng: &mut WyRand) -> String { - let base = vec![(); rng.generate::() as usize]; + let base = vec![(); rng.generate_range::(5..15) as usize]; base.into_iter() .map(|_| rng.generate::() as u32) .filter_map(char::from_u32) @@ -45,19 +48,19 @@ impl TestBench { TestBench { a: rand_string(rng), b: rng.generate::(), - c: (0..rng.generate::()) + c: (0..rng.generate_range::(5..15)) .map(|_| rand_string(rng)) .into_iter() .collect(), - d: (0..rng.generate::()) + d: (0..rng.generate_range::(5..15)) .map(|_| rand_string(rng)) .into_iter() .collect(), - e: (0..rng.generate::()) + e: (0..rng.generate_range::(5..15)) .map(|_| (rng.generate::(), rand_string(rng))) .into_iter() .collect(), - f: (0..rng.generate::()) + f: (0..rng.generate_range::(5..15)) .map(|_| (rng.generate::(), rand_string(rng))) .into_iter() .collect(), @@ -68,25 +71,237 @@ impl TestBench { TestBench { a: rand_string_large(rng), b: rng.generate::(), - c: (0..rng.generate::()) + c: (0..rng.generate_range::(0..(u16::MAX / 5))) .map(|_| rand_string(rng)) .into_iter() .collect(), - d: (0..rng.generate::()) + d: (0..rng.generate_range::(0..(u16::MAX / 5))) .map(|_| rand_string(rng)) .into_iter() .collect(), - e: (0..rng.generate::()) + e: (0..rng.generate_range::(0..(u16::MAX / 5))) .map(|_| (rng.generate::(), rand_string(rng))) .into_iter() .collect(), - f: (0..rng.generate::()) + f: (0..rng.generate_range::(0..(u16::MAX / 5))) .map(|_| (rng.generate::(), rand_string(rng))) .into_iter() .collect(), } } + pub fn random_mutate(self, rng: &mut WyRand) -> Self { + match rng.generate_range(0..6) { + 0 => Self { + a: rand_string(rng), + ..self + }, + 1 => Self { + b: rng.generate::(), + ..self + }, + 2 => Self { + c: self + .c + .into_iter() + .filter(|_| rng.generate_range(0..100) < 30_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + rand_string(rng) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u8::MAX / 4))) + .map(|_| rand_string(rng)), + ) + .collect(), + ..self + }, + 3 => Self { + d: self + .d + .into_iter() + .filter(|_| rng.generate_range(0..100) < 30_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + rand_string(rng) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u8::MAX / 4))) + .map(|_| rand_string(rng)), + ) + .collect(), + ..self + }, + 4 => Self { + e: self + .e + .into_iter() + .filter(|_| rng.generate_range(0..100) < 25_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + (rng.generate::(), rand_string(rng)) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u8::MAX / 4))) + .map(|_| (rng.generate::(), rand_string(rng))), + ) + .collect(), + ..self + }, + 5 => Self { + f: self + .f + .into_iter() + .filter(|_| rng.generate_range(0..100) < 25_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + (rng.generate::(), rand_string(rng)) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u8::MAX / 4))) + .map(|_| (rng.generate::(), rand_string(rng))), + ) + .collect(), + ..self + }, + _ => self, + } + } + + pub fn random_mutate_large(self, rng: &mut WyRand) -> Self { + match rng.generate_range(0..6) { + 0 => Self { + a: rand_string_large(rng), + ..self + }, + 1 => Self { + b: rng.generate::(), + ..self + }, + 2 => Self { + c: self + .c + .into_iter() + .filter(|_| rng.generate_range(0..100) < 30_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + rand_string(rng) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u16::MAX / 5))) + .map(|_| rand_string(rng)), + ) + .collect(), + ..self + }, + 3 => Self { + d: self + .d + .into_iter() + .filter(|_| rng.generate_range(0..100) < 30_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + rand_string(rng) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u16::MAX / 5))) + .map(|_| rand_string(rng)), + ) + .collect(), + ..self + }, + 4 => Self { + e: self + .e + .into_iter() + .filter(|_| rng.generate_range(0..100) < 25_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + (rng.generate::(), rand_string(rng)) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u16::MAX / 5))) + .map(|_| (rng.generate::(), rand_string(rng))), + ) + .collect(), + ..self + }, + 5 => Self { + f: self + .f + .into_iter() + .filter(|_| rng.generate_range(0..100) < 25_u8) + .collect::>() + .into_iter() + .map(|v| { + if rng.generate_range(0..100) < 25_u8 { + (rng.generate::(), rand_string(rng)) + } else { + v + } + }) + .collect::>() + .into_iter() + .chain( + (0..rng.generate_range::(0..(u16::MAX / 5))) + .map(|_| (rng.generate::(), rand_string(rng))), + ) + .collect(), + ..self + }, + _ => self, + } + } + #[track_caller] pub fn assert_eq(self, right: TestBench, diff: &Vec<::Diff>) { assert_eq!(self.a, right.a, "{:?}", diff); @@ -105,46 +320,112 @@ impl TestBench { #[cfg(test)] mod size_tests { + use bincode::Options; + use super::*; #[test] - fn test_sizes() { - size_basic(); + fn test_sizes_basic() { + structdiff_size::size_basic(); #[cfg(feature = "compare")] - serde_diff_size::size_basic(); - size_large(); + { + serde_diff_size::size_basic(); + diff_struct_size::size_basic(); + } + } + + #[ignore] + #[test] + fn test_sizes_large() { + structdiff_size::size_large(); #[cfg(feature = "compare")] - serde_diff_size::size_large(); + { + serde_diff_size::size_large(); + diff_struct_size::size_large(); + } } - fn size_basic() { - let mut bytes = 0_u64; - let mut rng = WyRand::new(); - for _ in 0..100 { - let first = std::hint::black_box(TestBench::generate_random(&mut rng)); - let second = std::hint::black_box(TestBench::generate_random(&mut rng)); - let diff = StructDiff::diff(&first, &second); - bytes += bincode::serialized_size(&diff).unwrap(); + #[test] + fn test_sizes_basic_mut() { + structdiff_size_mut::size_basic_mut(); + #[cfg(feature = "compare")] + { + serde_diff_size_mut::size_basic_mut(); + diff_struct_size_mut::size_basic_mut(); + } + } + + #[ignore] + #[test] + fn test_sizes_large_mut() { + structdiff_size_mut::size_large_mut(); + #[cfg(feature = "compare")] + { + serde_diff_size_mut::size_large_mut(); + diff_struct_size_mut::size_large_mut(); } - println!("StructDiff - small: {} bytes", bytes as f64 / 100.0) } - fn size_large() { - let mut bytes = 0_u64; - let mut rng = WyRand::new(); - for _ in 0..100 { - let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); - let second = std::hint::black_box(TestBench::generate_random_large(&mut rng)); - let diff = StructDiff::diff(&first, &second); - bytes += bincode::serialized_size(&diff).unwrap(); + mod structdiff_size { + use super::*; + + pub fn size_basic() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + for _i in 0..100 { + let first = std::hint::black_box(TestBench::generate_random(&mut rng)); + let second = std::hint::black_box(TestBench::generate_random(&mut rng)); + let diff = StructDiff::diff(&first, &second); + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("StructDiff - small: {} bytes", bytes as f64 / 100.0) + } + + pub fn size_large() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + for _i in 0..100 { + let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + let second = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + bytes += bincode::serialized_size(&StructDiff::diff(&first, &second)).unwrap(); + } + println!("StructDiff - large: {} bytes", bytes as f64 / 100.0) } - println!("StructDiff - large: {} bytes", bytes as f64 / 100.0) } #[cfg(feature = "compare")] - mod serde_diff_size { - use bincode::Options; + mod diff_struct_size { + use diff::Diff; + + use super::*; + + pub fn size_basic() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + for _i in 0..100 { + let first = std::hint::black_box(TestBench::generate_random(&mut rng)); + let second = std::hint::black_box(TestBench::generate_random(&mut rng)); + let diff = Diff::diff(&first, &second); + bytes += bincode::serialized_size(&diff).unwrap(); + } + + println!("Diff-Struct - small: {} bytes", bytes as f64 / 100.0) + } + + pub fn size_large() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + let second = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + bytes += bincode::serialized_size(&Diff::diff(&first, &second)).unwrap(); + } + println!("Diff-Struct - large: {} bytes", bytes as f64 / 100.0) + } + } + #[cfg(feature = "compare")] + mod serde_diff_size { use super::*; pub fn size_basic() { @@ -182,7 +463,122 @@ mod size_tests { ); bytes += bincode::serialized_size(&diff).unwrap(); } - println!("Serde-Diff - small: {} bytes", bytes as f64 / 100.0) + println!("Serde-Diff - large: {} bytes", bytes as f64 / 100.0) + } + } + + mod structdiff_size_mut { + use super::*; + + pub fn size_basic_mut() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random(&mut rng)); + let second = std::hint::black_box(first.clone().random_mutate(&mut rng)); + let diff = StructDiff::diff(&first, &second); + + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("StructDiff - mut small: {} bytes", bytes as f64 / 100.0) + } + + pub fn size_large_mut() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + let second = std::hint::black_box(first.clone().random_mutate_large(&mut rng)); + let diff = StructDiff::diff(&first, &second); + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("StructDiff - mut large: {} bytes", bytes as f64 / 100.0) + } + } + + #[cfg(feature = "compare")] + mod diff_struct_size_mut { + use diff::Diff; + + use super::*; + + pub fn size_basic_mut() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + let options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random(&mut rng)); + let second = std::hint::black_box(first.clone().random_mutate(&mut rng)); + let diff = + std::hint::black_box(options.serialize(&Diff::diff(&first, &second)).unwrap()); + + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("Diff-Struct - mut small: {} bytes", bytes as f64 / 100.0) + } + + pub fn size_large_mut() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + let options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + let second = std::hint::black_box(first.clone().random_mutate_large(&mut rng)); + let diff = + std::hint::black_box(options.serialize(&Diff::diff(&first, &second)).unwrap()); + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("Diff-Struct - mut large: {} bytes", bytes as f64 / 100.0) + } + } + + #[cfg(feature = "compare")] + mod serde_diff_size_mut { + use bincode::Options; + + use super::*; + + pub fn size_basic_mut() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + let options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random(&mut rng)); + let second = std::hint::black_box(first.clone().random_mutate(&mut rng)); + let diff = std::hint::black_box( + options + .serialize(&serde_diff::Diff::serializable(&first, &second)) + .unwrap(), + ); + + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("Serde-Diff - mut small: {} bytes", bytes as f64 / 100.0) + } + + pub fn size_large_mut() { + let mut bytes = 0_u64; + let mut rng = WyRand::new(); + let options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes(); + for _ in 0..100 { + let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); + let second = std::hint::black_box(first.clone().random_mutate_large(&mut rng)); + let diff = std::hint::black_box( + options + .serialize(&serde_diff::Diff::serializable(&first, &second)) + .unwrap(), + ); + bytes += bincode::serialized_size(&diff).unwrap(); + } + println!("Serde-Diff - mut large: {} bytes", bytes as f64 / 100.0) } } }