Skip to content
Open
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
7 changes: 4 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, beta, nightly]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
override: true
components: clippy, rustfmt
- run: cargo build
- run: cargo test
- run: cargo fmt -- --check
if: ${{ matrix.os == 'ubuntu-latest' }}
- run: cargo clippy
continue-on-error: true
- run: cargo bench
if: ${{ matrix.os == 'ubuntu-latest' }}
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ homepage = "https://github.com/utkarshkukreti/diff.rs"
repository = "https://github.com/utkarshkukreti/diff.rs"

[dev-dependencies]
quickcheck = "1.0.3"
criterion = "0.5.1"
fastrand = "2.0.1"
quickcheck = "1.0.3"

[[bench]]
name = "benches"
Expand Down
106 changes: 69 additions & 37 deletions benches/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,53 @@ extern crate diff;

use criterion::Criterion;

criterion::criterion_group!(benches, bench_slice, bench_chars, bench_real_world);
criterion::criterion_group!(benches, bench_slice, bench_real_world);
criterion::criterion_main!(benches);

fn bench_slice(c: &mut Criterion) {
c.bench_function("empty", |b| {
let slice = [0u8; 0];
b.iter(|| ::diff::slice(&slice, &slice));
});
let mut rng = fastrand::Rng::with_seed(0);

c.bench_function("10 equal items", |b| {
let slice = [0u8; 10];
b.iter(|| ::diff::slice(&slice, &slice));
});
let left = (0..1000).map(|_| rng.u8(..)).collect::<Vec<_>>();

c.bench_function("10 non-equal items", |b| {
let (left, right) = ([0u8; 10], [1u8; 10]);
b.iter(|| ::diff::slice(&left, &right));
});
let swap_10 = swap(&left, 10, &mut rng);
let swap_50 = swap(&left, 50, &mut rng);
let swap_100 = swap(&left, 100, &mut rng);
let swap_500 = swap(&left, 500, &mut rng);
let swap_1000 = swap(&left, 1000, &mut rng);

c.bench_function("100 equal items", |b| {
let slice = [0u8; 100];
b.iter(|| ::diff::slice(&slice, &slice));
});
for (name, vec) in [
("swap_10", &swap_10),
("swap_50", &swap_50),
("swap_100", &swap_100),
("swap_500", &swap_500),
("swap_1000", &swap_1000),
] {
assert_eq!(
::diff::slice(&left, vec).len(),
::diff::myers::slice(&left, vec).len()
);

c.bench_function("100 non-equal items", |b| {
let (left, right) = ([0u8; 100], [1u8; 100]);
b.iter(|| ::diff::slice(&left, &right));
});
let diffs = ::diff::slice(&left, vec).len() - left.len();

c.bench_function("1000 equal items", |b| {
let slice = [0u8; 1000];
b.iter(|| ::diff::slice(&slice, &slice));
});
c.bench_function(&format!("diff::slice {} ({} diffs)", name, diffs), |b| {
b.iter(|| ::diff::slice(&left, &vec));
});

c.bench_function("1000 non-equal items", |b| {
let (left, right) = ([0u8; 1000], [1u8; 1000]);
b.iter(|| ::diff::slice(&left, &right));
});
}
c.bench_function(
&format!("diff::myers::slice {} ({} diffs)", name, diffs),
|b| {
b.iter(|| ::diff::myers::slice(&left, &vec));
},
);
}

fn bench_chars(c: &mut Criterion) {
c.bench_function("1024 byte string, last 256 different", |b| {
let left = "?".repeat(768) + &"_".repeat(256);
let right = "?".repeat(768) + &"!".repeat(256);
assert_eq!(left.len(), right.len());
b.iter(|| ::diff::chars(&left, &right));
});
fn swap<T: Clone>(slice: &[T], swaps: usize, rng: &mut fastrand::Rng) -> Vec<T> {
let mut vec = slice.to_vec();
for _ in 0..swaps {
vec.swap(rng.usize(..slice.len()), rng.usize(..slice.len()));
}
vec
}
}

fn bench_real_world(c: &mut Criterion) {
Expand All @@ -70,6 +70,22 @@ fn bench_real_world(c: &mut Criterion) {
})
});

c.bench_function(
"diff::myers::lines on gitignore files from rust-lang/rust",
|b| {
b.iter(|| {
for (i, left) in gitignores.iter().enumerate() {
// diff with previous 3, itself, and next 3
for right in
gitignores[i.saturating_sub(3)..(i + 3).min(gitignores.len())].iter()
{
::diff::myers::lines(&left, &right);
}
}
})
},
);

c.bench_function("diff::chars on gitignore files from rust-lang/rust", |b| {
b.iter(|| {
for (i, left) in gitignores.iter().enumerate() {
Expand All @@ -80,4 +96,20 @@ fn bench_real_world(c: &mut Criterion) {
}
})
});

c.bench_function(
"diff::myers::chars on gitignore files from rust-lang/rust",
|b| {
b.iter(|| {
for (i, left) in gitignores.iter().enumerate() {
// diff with previous 2, itself, and next 2
for right in
gitignores[i.saturating_sub(2)..(i + 2).min(gitignores.len())].iter()
{
::diff::myers::chars(&left, &right);
}
}
})
},
);
}
62 changes: 52 additions & 10 deletions examples/diff.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,63 @@
extern crate diff;

fn main() {
let args = std::env::args().collect::<Vec<_>>();
let mut args = std::env::args().skip(1).collect::<Vec<_>>();

if args.len() != 3 {
println!("usage: cargo run --example diff <first file> <second file>");
let myers = args.iter().any(|arg| arg == "--myers");

let chars = args.iter().any(|arg| arg == "--chars");

args.retain(|arg| arg != "--myers" && arg != "--chars");

if args.len() != 2 {
println!("usage: cargo run --example diff [--myers] <first file> <second file>");
std::process::exit(1);
}

let left = std::fs::read_to_string(&args[1]).unwrap();
let right = std::fs::read_to_string(&args[2]).unwrap();
let left = std::fs::read_to_string(&args[0]).unwrap();
let right = std::fs::read_to_string(&args[1]).unwrap();

if chars {
let diff = if myers {
diff::myers::chars(&left, &right)
} else {
diff::chars(&left, &right)
};

let mut close = None;

for d in diff {
match (d, close) {
(diff::Result::Left(l), Some("-]")) => print!("{}", l),
(diff::Result::Left(l), open_) => {
print!("{}[-{}", open_.unwrap_or(""), l);
close = Some("-]");
}
(diff::Result::Right(r), Some("+}")) => print!("{}", r),
(diff::Result::Right(r), open_) => {
print!("{}{{+{}", open_.unwrap_or(""), r);
close = Some("+}");
}
(diff::Result::Both(l, _), Some(open_)) => {
print!("{}{}", open_, l);
close = None;
}
(diff::Result::Both(l, _), None) => print!("{}", l),
}
}
} else {
let diff = if myers {
diff::myers::lines(&left, &right)
} else {
diff::lines(&left, &right)
};

for diff in diff::lines(&left, &right) {
match diff {
diff::Result::Left(l) => println!("-{}", l),
diff::Result::Both(l, _) => println!(" {}", l),
diff::Result::Right(r) => println!("+{}", r),
for d in diff {
match d {
diff::Result::Left(l) => println!("-{}", l),
diff::Result::Both(l, _) => println!(" {}", l),
diff::Result::Right(r) => println!("+{}", r),
}
}
}
}
Loading