performance_test/
performance_test.rs1use pptx_to_md::{ParserConfig, PresentationContainer, Result};
13use rayon::prelude::*;
14use std::env;
15use std::path::Path;
16use std::time::{Duration, Instant};
17
18struct Benchmark {
19 name: String,
20 start_time: Instant,
21 results: Vec<Duration>,
22}
23
24impl Benchmark {
25 fn new(name: &str) -> Self {
26 println!("Starting benchmark: {}", name);
27 Benchmark {
28 name: name.to_string(),
29 start_time: Instant::now(),
30 results: Vec::new(),
31 }
32 }
33
34 fn measure<F, T>(&mut self, mut f: F) -> T
35 where
36 F: FnMut() -> T,
37 {
38 let start = Instant::now();
39 let result = f();
40 let duration = start.elapsed();
41 self.results.push(duration);
42 println!(" Operation took: {:?}", duration);
43 result
44 }
45
46 fn report(&self) {
47 if self.results.is_empty() {
48 println!("No measurements for {}", self.name);
49 return;
50 }
51
52 let total = self.start_time.elapsed();
53 let count = self.results.len();
54 let sum: Duration = self.results.iter().sum();
55 let avg = sum / count as u32;
56 let min = self.results.iter().min().unwrap();
57 let max = self.results.iter().max().unwrap();
58
59 println!("\nBenchmark Results for {}", self.name);
60 println!("----------------------------");
61 println!("Total time: {:?}", total);
62 println!("Operations: {}", count);
63 println!("Average time per operation: {:?}", avg);
64 println!("Min time: {:?}", min);
65 println!("Max time: {:?}", max);
66 println!("----------------------------\n");
67 }
68}
69
70fn main() -> Result<()> {
71 let args: Vec<String> = env::args().collect();
73 let pptx_path = if args.len() > 1 {
74 &args[1]
75 } else {
76 eprintln!(
77 "Usage: cargo run --example performance_test <presentation.pptx|presentation.odp> [iterations]"
78 );
79 return Ok(());
80 };
81
82 let iterations = if args.len() > 2 {
83 args[2].parse().unwrap_or(5)
84 } else {
85 10 };
87
88 println!(
89 "Performance testing with {} iterations on: {}",
90 iterations, pptx_path
91 );
92
93 let mut single_thread_bench = Benchmark::new("Single-threaded parsing");
95
96 let mut total_slides = 0;
97
98 for i in 0..iterations {
99 println!("\nIteration {} (Single-threaded)", i + 1);
100
101 let mut container = single_thread_bench.measure(|| {
103 let config = ParserConfig::builder().extract_images(true).build();
104 PresentationContainer::open(Path::new(pptx_path), config)
105 .expect("Failed to open presentation")
106 });
107
108 let slides =
110 single_thread_bench.measure(|| container.parse_all().expect("Failed to parse slides"));
111 println!(" Found {} slides in the presentation", slides.len());
112
113 let _md_content = single_thread_bench.measure(|| {
115 slides
116 .iter()
117 .filter_map(|slide| slide.convert_to_md().ok())
118 .collect::<Vec<String>>()
119 });
120
121 total_slides += slides.len();
122 }
123
124 single_thread_bench.report();
125 println!(
126 "Average slides per presentation: {}",
127 total_slides / iterations
128 );
129
130 let mut single_thread_streamed_bench = Benchmark::new("Single-threaded streamed parsing");
132
133 total_slides = 0;
134
135 for i in 0..iterations {
136 println!("\nIteration {} (Single-threaded streamed)", i + 1);
137
138 let mut container = single_thread_streamed_bench.measure(|| {
140 let config = ParserConfig::builder().extract_images(true).build();
141 PresentationContainer::open(Path::new(pptx_path), config)
142 .expect("Failed to open presentation")
143 });
144
145 let slides_processed = single_thread_streamed_bench.measure(|| {
147 let mut processed = 0;
148
149 for slide_result in container.iter_slides() {
151 match slide_result {
152 Ok(slide) => {
153 let _md_content = slide.convert_to_md();
155 processed += 1;
156 }
157 Err(e) => {
158 eprintln!("Error processing slide: {:?}", e);
159 }
160 }
161 }
162
163 processed
164 });
165
166 println!(" Processed {} slides", slides_processed);
167 total_slides += slides_processed;
168 }
169
170 single_thread_streamed_bench.report();
171 println!(
172 "Average slides per presentation: {}",
173 total_slides / iterations
174 );
175
176 let mut optimized_multi_thread_bench = Benchmark::new("Optimized Multi-threaded parsing");
178
179 total_slides = 0;
180
181 for i in 0..iterations {
182 println!("\nIteration {} (Optimized Multi-threaded)", i + 1);
183
184 let mut container = optimized_multi_thread_bench.measure(|| {
186 let config = ParserConfig::builder().extract_images(true).build();
187 PresentationContainer::open(Path::new(pptx_path), config)
188 .expect("Failed to open presentation")
189 });
190
191 let slides = optimized_multi_thread_bench.measure(|| {
192 container
193 .parse_all_multi_threaded()
194 .expect("Failed to parse slides")
195 });
196
197 println!(" Successfully processed {} slides", slides.len());
198
199 let _md_content = optimized_multi_thread_bench.measure(|| {
201 slides
202 .par_iter()
203 .filter_map(|slide| slide.convert_to_md().ok())
204 .collect::<Vec<String>>()
205 });
206
207 total_slides += slides.len();
208 }
209
210 optimized_multi_thread_bench.report();
211 println!(
212 "Average slides per presentation: {}",
213 total_slides / iterations
214 );
215
216 if !single_thread_bench.results.is_empty()
218 && !single_thread_streamed_bench.results.is_empty()
219 && !optimized_multi_thread_bench.results.is_empty()
220 {
221 let single_avg: Duration = single_thread_bench.results.iter().sum::<Duration>()
222 / single_thread_bench.results.len() as u32;
223 let single_streamed_avg: Duration = single_thread_streamed_bench
224 .results
225 .iter()
226 .sum::<Duration>()
227 / single_thread_streamed_bench.results.len() as u32;
228 let optimized_multi_avg: Duration = optimized_multi_thread_bench
229 .results
230 .iter()
231 .sum::<Duration>()
232 / optimized_multi_thread_bench.results.len() as u32;
233
234 println!("\nPerformance Comparison");
235 println!("=====================");
236 println!("Single-threaded average: {:?}", single_avg);
237 println!(
238 "Single-threaded streaming average: {:?}",
239 single_streamed_avg
240 );
241 println!(
242 "Optimized multi-threaded average: {:?}",
243 optimized_multi_avg
244 );
245
246 if single_avg > single_streamed_avg {
248 let speedup = single_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
249 println!(
250 "Single-threaded streaming is {:.2}x faster than single-threaded",
251 speedup
252 );
253 } else {
254 let slowdown = single_streamed_avg.as_secs_f64() / single_avg.as_secs_f64();
255 println!(
256 "Single-threaded streaming is {:.2}x slower than single-threaded",
257 slowdown
258 );
259 }
260
261 if single_avg > optimized_multi_avg {
263 let speedup = single_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
264 println!(
265 "Optimized multi-threaded is {:.2}x faster than single-threaded",
266 speedup
267 );
268 } else {
269 let slowdown = optimized_multi_avg.as_secs_f64() / single_avg.as_secs_f64();
270 println!(
271 "Optimized multi-threaded is {:.2}x slower than single-threaded",
272 slowdown
273 );
274 }
275
276 if single_streamed_avg > optimized_multi_avg {
278 let speedup = single_streamed_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
279 println!(
280 "Optimized multi-threaded is {:.2}x faster than single-threaded streaming",
281 speedup
282 );
283 } else {
284 let slowdown = optimized_multi_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
285 println!(
286 "Optimized multi-threaded is {:.2}x slower than single-threaded streaming",
287 slowdown
288 );
289 }
290
291 let fastest_approach = if single_avg <= single_streamed_avg
293 && single_avg <= optimized_multi_avg
294 {
295 "Single-threaded"
296 } else if single_streamed_avg <= single_avg && single_streamed_avg <= optimized_multi_avg {
297 "Single-threaded streaming"
298 } else {
299 "Optimized multi-threaded"
300 };
301
302 println!(
303 "\nOverall result: {} approach is the fastest for this workload.",
304 fastest_approach
305 );
306 }
307
308 Ok(())
309}