use pptx_to_md::{ParserConfig, PresentationContainer, Result};
use rayon::prelude::*;
use std::env;
use std::path::Path;
use std::time::{Duration, Instant};
struct Benchmark {
name: String,
start_time: Instant,
results: Vec<Duration>,
}
impl Benchmark {
fn new(name: &str) -> Self {
println!("Starting benchmark: {}", name);
Benchmark {
name: name.to_string(),
start_time: Instant::now(),
results: Vec::new(),
}
}
fn measure<F, T>(&mut self, mut f: F) -> T
where
F: FnMut() -> T,
{
let start = Instant::now();
let result = f();
let duration = start.elapsed();
self.results.push(duration);
println!(" Operation took: {:?}", duration);
result
}
fn report(&self) {
if self.results.is_empty() {
println!("No measurements for {}", self.name);
return;
}
let total = self.start_time.elapsed();
let count = self.results.len();
let sum: Duration = self.results.iter().sum();
let avg = sum / count as u32;
let min = self.results.iter().min().unwrap();
let max = self.results.iter().max().unwrap();
println!("\nBenchmark Results for {}", self.name);
println!("----------------------------");
println!("Total time: {:?}", total);
println!("Operations: {}", count);
println!("Average time per operation: {:?}", avg);
println!("Min time: {:?}", min);
println!("Max time: {:?}", max);
println!("----------------------------\n");
}
}
fn main() -> Result<()> {
let args: Vec<String> = env::args().collect();
let pptx_path = if args.len() > 1 {
&args[1]
} else {
eprintln!(
"Usage: cargo run --example performance_test <presentation.pptx|presentation.odp> [iterations]"
);
return Ok(());
};
let iterations = if args.len() > 2 {
args[2].parse().unwrap_or(5)
} else {
10 };
println!(
"Performance testing with {} iterations on: {}",
iterations, pptx_path
);
let mut single_thread_bench = Benchmark::new("Single-threaded parsing");
let mut total_slides = 0;
for i in 0..iterations {
println!("\nIteration {} (Single-threaded)", i + 1);
let mut container = single_thread_bench.measure(|| {
let config = ParserConfig::builder().extract_images(true).build();
PresentationContainer::open(Path::new(pptx_path), config)
.expect("Failed to open presentation")
});
let slides =
single_thread_bench.measure(|| container.parse_all().expect("Failed to parse slides"));
println!(" Found {} slides in the presentation", slides.len());
let _md_content = single_thread_bench.measure(|| {
slides
.iter()
.filter_map(|slide| slide.convert_to_md().ok())
.collect::<Vec<String>>()
});
total_slides += slides.len();
}
single_thread_bench.report();
println!(
"Average slides per presentation: {}",
total_slides / iterations
);
let mut single_thread_streamed_bench = Benchmark::new("Single-threaded streamed parsing");
total_slides = 0;
for i in 0..iterations {
println!("\nIteration {} (Single-threaded streamed)", i + 1);
let mut container = single_thread_streamed_bench.measure(|| {
let config = ParserConfig::builder().extract_images(true).build();
PresentationContainer::open(Path::new(pptx_path), config)
.expect("Failed to open presentation")
});
let slides_processed = single_thread_streamed_bench.measure(|| {
let mut processed = 0;
for slide_result in container.iter_slides() {
match slide_result {
Ok(slide) => {
let _md_content = slide.convert_to_md();
processed += 1;
}
Err(e) => {
eprintln!("Error processing slide: {:?}", e);
}
}
}
processed
});
println!(" Processed {} slides", slides_processed);
total_slides += slides_processed;
}
single_thread_streamed_bench.report();
println!(
"Average slides per presentation: {}",
total_slides / iterations
);
let mut optimized_multi_thread_bench = Benchmark::new("Optimized Multi-threaded parsing");
total_slides = 0;
for i in 0..iterations {
println!("\nIteration {} (Optimized Multi-threaded)", i + 1);
let mut container = optimized_multi_thread_bench.measure(|| {
let config = ParserConfig::builder().extract_images(true).build();
PresentationContainer::open(Path::new(pptx_path), config)
.expect("Failed to open presentation")
});
let slides = optimized_multi_thread_bench.measure(|| {
container
.parse_all_multi_threaded()
.expect("Failed to parse slides")
});
println!(" Successfully processed {} slides", slides.len());
let _md_content = optimized_multi_thread_bench.measure(|| {
slides
.par_iter()
.filter_map(|slide| slide.convert_to_md().ok())
.collect::<Vec<String>>()
});
total_slides += slides.len();
}
optimized_multi_thread_bench.report();
println!(
"Average slides per presentation: {}",
total_slides / iterations
);
if !single_thread_bench.results.is_empty()
&& !single_thread_streamed_bench.results.is_empty()
&& !optimized_multi_thread_bench.results.is_empty()
{
let single_avg: Duration = single_thread_bench.results.iter().sum::<Duration>()
/ single_thread_bench.results.len() as u32;
let single_streamed_avg: Duration = single_thread_streamed_bench
.results
.iter()
.sum::<Duration>()
/ single_thread_streamed_bench.results.len() as u32;
let optimized_multi_avg: Duration = optimized_multi_thread_bench
.results
.iter()
.sum::<Duration>()
/ optimized_multi_thread_bench.results.len() as u32;
println!("\nPerformance Comparison");
println!("=====================");
println!("Single-threaded average: {:?}", single_avg);
println!(
"Single-threaded streaming average: {:?}",
single_streamed_avg
);
println!(
"Optimized multi-threaded average: {:?}",
optimized_multi_avg
);
if single_avg > single_streamed_avg {
let speedup = single_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
println!(
"Single-threaded streaming is {:.2}x faster than single-threaded",
speedup
);
} else {
let slowdown = single_streamed_avg.as_secs_f64() / single_avg.as_secs_f64();
println!(
"Single-threaded streaming is {:.2}x slower than single-threaded",
slowdown
);
}
if single_avg > optimized_multi_avg {
let speedup = single_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
println!(
"Optimized multi-threaded is {:.2}x faster than single-threaded",
speedup
);
} else {
let slowdown = optimized_multi_avg.as_secs_f64() / single_avg.as_secs_f64();
println!(
"Optimized multi-threaded is {:.2}x slower than single-threaded",
slowdown
);
}
if single_streamed_avg > optimized_multi_avg {
let speedup = single_streamed_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
println!(
"Optimized multi-threaded is {:.2}x faster than single-threaded streaming",
speedup
);
} else {
let slowdown = optimized_multi_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
println!(
"Optimized multi-threaded is {:.2}x slower than single-threaded streaming",
slowdown
);
}
let fastest_approach = if single_avg <= single_streamed_avg
&& single_avg <= optimized_multi_avg
{
"Single-threaded"
} else if single_streamed_avg <= single_avg && single_streamed_avg <= optimized_multi_avg {
"Single-threaded streaming"
} else {
"Optimized multi-threaded"
};
println!(
"\nOverall result: {} approach is the fastest for this workload.",
fastest_approach
);
}
Ok(())
}