From a8be45d1417ee8ed0531c80a1f09d1d0bfe6aee0 Mon Sep 17 00:00:00 2001 From: "Rahul P. Tekawade" <38859477+tekawade@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:59:11 -0800 Subject: [PATCH] feat: Add performance benchmarks for various modules and expose fundamental submodules. --- Cargo.toml | 1 + README.md | 37 ++-- modules/advanced/Cargo.toml | 6 + .../advanced/benches/advanced_benchmarks.rs | 30 +++ modules/fundamentals/Cargo.toml | 6 + .../benches/fundamentals_benchmarks.rs | 174 ++++++++++++++++++ modules/fundamentals/src/collections/mod.rs | 12 +- modules/fundamentals/src/union_find/mod.rs | 6 +- modules/geometry/Cargo.toml | 6 + .../geometry/benches/geometry_benchmarks.rs | 26 +++ modules/searching/Cargo.toml | 6 + .../searching/benches/searching_benchmarks.rs | 115 ++++++++++++ modules/sorting/Cargo.toml | 6 + modules/sorting/benches/sorting_benchmarks.rs | 105 +++++++++++ modules/strings/Cargo.toml | 6 + modules/strings/benches/strings_benchmarks.rs | 32 ++++ 16 files changed, 551 insertions(+), 23 deletions(-) create mode 100644 modules/advanced/benches/advanced_benchmarks.rs create mode 100644 modules/fundamentals/benches/fundamentals_benchmarks.rs create mode 100644 modules/geometry/benches/geometry_benchmarks.rs create mode 100644 modules/searching/benches/searching_benchmarks.rs create mode 100644 modules/sorting/benches/sorting_benchmarks.rs create mode 100644 modules/strings/benches/strings_benchmarks.rs diff --git a/Cargo.toml b/Cargo.toml index be610d6..68f4d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ license = "GPL-3.0" repository = "https://github.com/tekawade/algs4-rust" keywords = ["algorithms", "data-structures", "education"] categories = ["algorithms", "data-structures"] +rust-version = "1.82.0" [workspace.dependencies] # Common dependencies shared across modules diff --git a/README.md b/README.md index 9f5bed6..41aa9dd 100644 --- a/README.md +++ b/README.md @@ -517,20 +517,29 @@ cargo test --doc ``` ### Benchmarking - -```bash -# Run graph benchmarks -cargo bench -p algs4-graphs - -# Benchmarks include: -# - Graph creation -# - DFS/BFS traversal -# - Connected components -# - Dijkstra shortest paths -# - Minimum spanning trees -# - Topological sort -# - Strongly connected components -``` + + ```bash + # Run benchmarks for specific module + cargo bench -p algs4-fundamentals + cargo bench -p algs4-sorting + cargo bench -p algs4-searching + cargo bench -p algs4-graphs + cargo bench -p algs4-strings + cargo bench -p algs4-geometry + cargo bench -p algs4-advanced + + # Run all benchmarks (may take a while) + cargo bench --workspace + + # Benchmarks include: + # - Fundamentals: Bags, Queues, Stacks, Union-Find + # - Sorting: All major sorting algorithms (Elementary, Merge, Quick, Heap, String Sorts) + # - Searching: BST, Red-Black BST, Hash Tables, Tries + # - Graphs: Traversal, Shortest Paths, MST, etc. + # - Strings: Pattern Matching (KMP), Tries + # - Geometry: Convex Hull + # - Advanced: Fenwick Tree, B-Tree + ``` ### Documentation diff --git a/modules/advanced/Cargo.toml b/modules/advanced/Cargo.toml index a805a47..984aa63 100644 --- a/modules/advanced/Cargo.toml +++ b/modules/advanced/Cargo.toml @@ -15,3 +15,9 @@ algs4-searching = { version = "0.1.0", path = "../searching" } algs4-graphs = { version = "0.1.0", path = "../graphs" } [dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "advanced_benchmarks" +harness = false diff --git a/modules/advanced/benches/advanced_benchmarks.rs b/modules/advanced/benches/advanced_benchmarks.rs new file mode 100644 index 0000000..f0d997a --- /dev/null +++ b/modules/advanced/benches/advanced_benchmarks.rs @@ -0,0 +1,30 @@ +use algs4_advanced::fenwick_tree::FenwickTree; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::prelude::SliceRandom; +use rand::thread_rng; + +fn advanced_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("advanced"); + + for size in [1000, 5000].iter() { + let mut rng = thread_rng(); + let mut data: Vec = (0..*size as i32).collect(); + data.shuffle(&mut rng); + + group.bench_with_input( + BenchmarkId::new("fenwick_tree_sum", size), + size, + |b, &size| { + b.iter(|| { + let data = vec![1; size]; + let ft = FenwickTree::new(black_box(data)); + ft.rsq(size - 1) + }); + }, + ); + } + group.finish(); +} + +criterion_group!(benches, advanced_benchmark); +criterion_main!(benches); diff --git a/modules/fundamentals/Cargo.toml b/modules/fundamentals/Cargo.toml index 0c693bc..e31ea61 100644 --- a/modules/fundamentals/Cargo.toml +++ b/modules/fundamentals/Cargo.toml @@ -16,3 +16,9 @@ lazy_static = "1.5" byteorder = { workspace = true } [dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "fundamentals_benchmarks" +harness = false diff --git a/modules/fundamentals/benches/fundamentals_benchmarks.rs b/modules/fundamentals/benches/fundamentals_benchmarks.rs new file mode 100644 index 0000000..56d8f3a --- /dev/null +++ b/modules/fundamentals/benches/fundamentals_benchmarks.rs @@ -0,0 +1,174 @@ +use algs4_fundamentals::collections::linked_bag::LinkedBag; +use algs4_fundamentals::collections::linked_queue::LinkedQueue; +use algs4_fundamentals::collections::linked_stack::LinkedStack; +use algs4_fundamentals::collections::resizing_array_bag::ResizingArrayBag; +use algs4_fundamentals::collections::resizing_array_queue::ResizingArrayQueue; +use algs4_fundamentals::collections::resizing_array_stack::ResizingArrayStack; +use algs4_fundamentals::union_find::quick_find_uf::QuickFindUF; +use algs4_fundamentals::union_find::quick_union_uf::QuickUnionUF; +use algs4_fundamentals::union_find::weighted_quick_union_uf::WeightedQuickUnionUF; +use algs4_fundamentals::union_find::weighted_quick_union_uf::WeightedQuickUnionUF as UF; // UF trait might not be public or exist as named +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; + +fn bag_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("bag"); + + for size in [1000, 10000].iter() { + group.bench_with_input( + BenchmarkId::new("linked_bag_add", size), + size, + |b, &size| { + b.iter(|| { + let mut bag = LinkedBag::new(); + for i in 0..size { + bag.add(black_box(i)); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("resizing_array_bag_add", size), + size, + |b, &size| { + b.iter(|| { + let mut bag = ResizingArrayBag::new(); + for i in 0..size { + bag.add(black_box(i)); + } + }); + }, + ); + } + group.finish(); +} + +fn queue_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("queue"); + + for size in [1000, 10000].iter() { + group.bench_with_input( + BenchmarkId::new("linked_queue_enqueue_dequeue", size), + size, + |b, &size| { + b.iter(|| { + let mut queue = LinkedQueue::new(); + for i in 0..size { + queue.enqueue(black_box(i)); + } + for _ in 0..size { + queue.dequeue(); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("resizing_array_queue_enqueue_dequeue", size), + size, + |b, &size| { + b.iter(|| { + let mut queue = ResizingArrayQueue::new(); + for i in 0..size { + queue.enqueue(black_box(i)); + } + for _ in 0..size { + queue.dequeue(); + } + }); + }, + ); + } + group.finish(); +} + +fn stack_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("stack"); + + for size in [1000, 10000].iter() { + group.bench_with_input( + BenchmarkId::new("linked_stack_push_pop", size), + size, + |b, &size| { + b.iter(|| { + let mut stack = LinkedStack::new(); + for i in 0..size { + stack.push(black_box(i)); + } + for _ in 0..size { + stack.pop(); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("resizing_array_stack_push_pop", size), + size, + |b, &size| { + b.iter(|| { + let mut stack = ResizingArrayStack::new(); + for i in 0..size { + stack.push(black_box(i)); + } + for _ in 0..size { + stack.pop(); + } + }); + }, + ); + } + group.finish(); +} + +fn union_find_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("union_find"); + + for size in [1000, 5000].iter() { + let ops = size * 2; + + group.bench_with_input(BenchmarkId::new("quick_find", size), size, |b, &size| { + b.iter(|| { + let mut uf = QuickFindUF::new(black_box(size)); + for i in 0..size - 1 { + uf.union(i, i + 1); + } + uf.connected(0, size - 1) + }); + }); + + group.bench_with_input(BenchmarkId::new("quick_union", size), size, |b, &size| { + b.iter(|| { + let mut uf = QuickUnionUF::new(black_box(size)); + for i in 0..size - 1 { + uf.union(i, i + 1); + } + uf.connected(0, size - 1) + }); + }); + + group.bench_with_input( + BenchmarkId::new("weighted_quick_union", size), + size, + |b, &size| { + b.iter(|| { + let mut uf = WeightedQuickUnionUF::new(black_box(size)); + for i in 0..size - 1 { + uf.union(i, i + 1); + } + uf.connected(0, size - 1) + }); + }, + ); + } + group.finish(); +} + +criterion_group!( + benches, + bag_benchmark, + queue_benchmark, + stack_benchmark, + union_find_benchmark +); +criterion_main!(benches); diff --git a/modules/fundamentals/src/collections/mod.rs b/modules/fundamentals/src/collections/mod.rs index 6fff9d4..e9a40bc 100644 --- a/modules/fundamentals/src/collections/mod.rs +++ b/modules/fundamentals/src/collections/mod.rs @@ -12,18 +12,18 @@ //! **Reference:** // Linked implementations -mod linked_bag; -mod linked_queue; -mod linked_stack; +pub mod linked_bag; +pub mod linked_queue; +pub mod linked_stack; pub use linked_bag::LinkedBag; pub use linked_queue::LinkedQueue; pub use linked_stack::LinkedStack; // Array implementations -mod resizing_array_bag; -mod resizing_array_queue; -mod resizing_array_stack; +pub mod resizing_array_bag; +pub mod resizing_array_queue; +pub mod resizing_array_stack; pub use resizing_array_bag::ResizingArrayBag; pub use resizing_array_queue::ResizingArrayQueue; diff --git a/modules/fundamentals/src/union_find/mod.rs b/modules/fundamentals/src/union_find/mod.rs index 4f82bab..438d748 100644 --- a/modules/fundamentals/src/union_find/mod.rs +++ b/modules/fundamentals/src/union_find/mod.rs @@ -39,9 +39,9 @@ //! //! **Reference:** -mod quick_find_uf; -mod quick_union_uf; -mod weighted_quick_union_uf; +pub mod quick_find_uf; +pub mod quick_union_uf; +pub mod weighted_quick_union_uf; pub use quick_find_uf::QuickFindUF; pub use quick_union_uf::QuickUnionUF; diff --git a/modules/geometry/Cargo.toml b/modules/geometry/Cargo.toml index 7f0f480..ea6362c 100644 --- a/modules/geometry/Cargo.toml +++ b/modules/geometry/Cargo.toml @@ -13,3 +13,9 @@ description = "Geometric algorithms from Algorithms 4th Edition" algs4-fundamentals = { version = "0.1.0", path = "../fundamentals" } [dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "geometry_benchmarks" +harness = false diff --git a/modules/geometry/benches/geometry_benchmarks.rs b/modules/geometry/benches/geometry_benchmarks.rs new file mode 100644 index 0000000..90380d4 --- /dev/null +++ b/modules/geometry/benches/geometry_benchmarks.rs @@ -0,0 +1,26 @@ +use algs4_geometry::graham_scan::GrahamScan; +use algs4_geometry::point2d::Point2D; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::{thread_rng, Rng}; + +fn geometry_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("geometry"); + + for size in [1000, 5000].iter() { + let mut rng = thread_rng(); + let points: Vec = (0..*size) + .map(|_| Point2D::new(rng.gen_range(0.0..100.0), rng.gen_range(0.0..100.0))) + .collect(); + + group.bench_with_input(BenchmarkId::new("graham_scan", size), &points, |b, pts| { + b.iter(|| { + let mut p = pts.clone(); + GrahamScan::new(black_box(&mut p)); + }); + }); + } + group.finish(); +} + +criterion_group!(benches, geometry_benchmark); +criterion_main!(benches); diff --git a/modules/searching/Cargo.toml b/modules/searching/Cargo.toml index 03be2aa..42cf34d 100644 --- a/modules/searching/Cargo.toml +++ b/modules/searching/Cargo.toml @@ -13,3 +13,9 @@ description = "Searching algorithms and symbol tables from Algorithms 4th Editio algs4-fundamentals = { version = "0.1.0", path = "../fundamentals" } [dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "searching_benchmarks" +harness = false diff --git a/modules/searching/benches/searching_benchmarks.rs b/modules/searching/benches/searching_benchmarks.rs new file mode 100644 index 0000000..2d0a091 --- /dev/null +++ b/modules/searching/benches/searching_benchmarks.rs @@ -0,0 +1,115 @@ +use algs4_searching::bst::BST; +use algs4_searching::linear_probing_hash_st::LinearProbingHashST; +use algs4_searching::red_black_bst::RedBlackBST; +use algs4_searching::separate_chaining_hash_st::SeparateChainingHashST; +use algs4_searching::trie_st::TrieST; +// use algs4_searching::btree::BTree; // BTree might be complex to init, skipping for now if not simple +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::distributions::Alphanumeric; +use rand::prelude::SliceRandom; +use rand::thread_rng; +use rand::Rng; + +fn searching_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("searching"); + + for size in [1000, 5000].iter() { + let mut rng = thread_rng(); + let mut keys: Vec = (0..*size as i32).collect(); + keys.shuffle(&mut rng); + + group.bench_with_input(BenchmarkId::new("bst_put_get", size), size, |b, &size| { + b.iter(|| { + let mut st = BST::new(); + for &key in &keys { + st.put(key, key); + } + for &key in &keys { + st.get(&key); + } + }); + }); + + group.bench_with_input( + BenchmarkId::new("red_black_bst_put_get", size), + size, + |b, &size| { + b.iter(|| { + let mut st = RedBlackBST::new(); + for &key in &keys { + st.put(key, key); + } + for &key in &keys { + st.get(&key); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("linear_probing_hash_st_put_get", size), + size, + |b, &size| { + b.iter(|| { + let mut st = LinearProbingHashST::new(); + for &key in &keys { + st.put(key, key); + } + for &key in &keys { + st.get(&key); + } + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("separate_chaining_hash_st_put_get", size), + size, + |b, &size| { + b.iter(|| { + let mut st = SeparateChainingHashST::new(); + for &key in &keys { + st.put(key, key); + } + for &key in &keys { + st.get(&key); + } + }); + }, + ); + } + + // Trie benchmark + for size in [1000, 5000].iter() { + let keys: Vec = (0..*size) + .map(|_| { + thread_rng() + .sample_iter(&Alphanumeric) + .take(10) + .map(char::from) + .collect() + }) + .collect(); + + group.bench_with_input( + BenchmarkId::new("trie_st_put_get", size), + size, + |b, &_size| { + b.iter(|| { + let mut st = TrieST::new(); + for (i, key) in keys.iter().enumerate() { + st.put(key, i); + } + for key in &keys { + st.get(key); + } + }); + }, + ); + } + + group.finish(); +} + +criterion_group!(benches, searching_benchmark); +criterion_main!(benches); diff --git a/modules/sorting/Cargo.toml b/modules/sorting/Cargo.toml index 0909738..a2de23d 100644 --- a/modules/sorting/Cargo.toml +++ b/modules/sorting/Cargo.toml @@ -13,3 +13,9 @@ description = "Sorting algorithms from Algorithms 4th Edition" algs4-fundamentals = { version = "0.1.0", path = "../fundamentals" } [dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "sorting_benchmarks" +harness = false diff --git a/modules/sorting/benches/sorting_benchmarks.rs b/modules/sorting/benches/sorting_benchmarks.rs new file mode 100644 index 0000000..8f315fe --- /dev/null +++ b/modules/sorting/benches/sorting_benchmarks.rs @@ -0,0 +1,105 @@ +use algs4_sorting::heap; +use algs4_sorting::insertion; +use algs4_sorting::lsd; +use algs4_sorting::merge; +use algs4_sorting::msd; +use algs4_sorting::quick; +use algs4_sorting::quick_3string; +use algs4_sorting::selection; +use algs4_sorting::shell; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::distributions::Alphanumeric; +use rand::seq::SliceRandom; +use rand::thread_rng; +use rand::Rng; + +fn sorting_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("sorting"); + + for size in [100, 1000, 5000].iter() { + let mut rng = thread_rng(); + let mut data: Vec = (0..*size as i32).collect(); + data.shuffle(&mut rng); + + group.bench_with_input(BenchmarkId::new("selection", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + selection::sort(black_box(&mut arr)); + }); + }); + + group.bench_with_input(BenchmarkId::new("insertion", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + insertion::sort(black_box(&mut arr)); + }); + }); + + group.bench_with_input(BenchmarkId::new("shell", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + shell::sort(black_box(&mut arr)); + }); + }); + + group.bench_with_input(BenchmarkId::new("merge", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + merge::sort(black_box(&mut arr)); + }); + }); + + group.bench_with_input(BenchmarkId::new("quick", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + quick::sort(black_box(&mut arr)); + }); + }); + + group.bench_with_input(BenchmarkId::new("heap", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + heap::sort(black_box(&mut arr)); + }); + }); + } + + // String sorts + for size in [1000, 5000].iter() { + let data: Vec = (0..*size) + .map(|_| { + thread_rng() + .sample_iter(&Alphanumeric) + .take(10) + .map(char::from) + .collect() + }) + .collect(); + + group.bench_with_input(BenchmarkId::new("lsd", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + lsd::sort(&mut arr, 10); + }); + }); + + group.bench_with_input(BenchmarkId::new("msd", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + msd::sort(&mut arr); + }); + }); + + group.bench_with_input(BenchmarkId::new("quick3string", size), &data, |b, d| { + b.iter(|| { + let mut arr = d.clone(); + quick_3string::sort(&mut arr); + }); + }); + } + + group.finish(); +} + +criterion_group!(benches, sorting_benchmark); +criterion_main!(benches); diff --git a/modules/strings/Cargo.toml b/modules/strings/Cargo.toml index 6999b33..3ad02b6 100644 --- a/modules/strings/Cargo.toml +++ b/modules/strings/Cargo.toml @@ -14,3 +14,9 @@ algs4-fundamentals = { version = "0.1.0", path = "../fundamentals" } algs4-searching = { version = "0.1.0", path = "../searching" } [dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "strings_benchmarks" +harness = false diff --git a/modules/strings/benches/strings_benchmarks.rs b/modules/strings/benches/strings_benchmarks.rs new file mode 100644 index 0000000..42c85d8 --- /dev/null +++ b/modules/strings/benches/strings_benchmarks.rs @@ -0,0 +1,32 @@ +use algs4_strings::pattern_matching::kmp::KMP; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; + +fn pattern_matching_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("pattern_matching"); + + for size in [1000, 5000].iter() { + let text: String = (0..*size) + .map(|_| { + thread_rng() + .sample_iter(&Alphanumeric) + .take(1) + .map(char::from) + .collect::() + }) + .collect(); + let pattern = "ABC"; // Simple pattern + + group.bench_with_input(BenchmarkId::new("kmp_search", size), &text, |b, txt| { + b.iter(|| { + let kmp = KMP::new(black_box(pattern)); + kmp.search(black_box(txt)); + }); + }); + } + group.finish(); +} + +criterion_group!(benches, pattern_matching_benchmark); +criterion_main!(benches);