Skip to main content

performance_test/
performance_test.rs

1//! Performance benchmark example for the pptx-to-md crate
2//!
3//! This example measures and compares the performance of different parsing approaches:
4//! - Single-threaded parsing
5//! - Single-threading wih streaming
6//! - Optimized multithreaded parsing with Rayon
7//!
8//! It also provides timing for individual operations to identify bottlenecks.
9//!
10//! Run with: cargo run --release --example performance_test <presentation.pptx|presentation.odp> [iterations]
11
12use 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    // Get the PPTX file path and optional iteration count from command line arguments
72    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 // Default to 10 iterations
86    };
87
88    println!(
89        "Performance testing with {} iterations on: {}",
90        iterations, pptx_path
91    );
92
93    // =========== Single-threaded Approach ===========
94    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        // Measure container creation
102        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        // Measure parsing
109        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        // Measure conversion
114        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    // =========== Single-threaded Streamed Approach ===========
131    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        // Measure container creation
139        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        // Measure slide processing (including parsing and conversion)
146        let slides_processed = single_thread_streamed_bench.measure(|| {
147            let mut processed = 0;
148
149            // Process slides one by one using the iterator
150            for slide_result in container.iter_slides() {
151                match slide_result {
152                    Ok(slide) => {
153                        // Konvertiere den Slide zu Markdown
154                        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    // =========== Optimized Multi-threaded Approach ===========
177    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        // Container öffnen mit der gewünschten Konfiguration
185        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        // Parallel zu Markdown konvertieren (bleibt unverändert)
200        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    // =========== Performance Comparison ===========
217    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        // Compare single-threaded vs single-threaded streaming
247        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        // Compare single-threaded vs optimized multithreaded
262        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        // Compare single-threaded streaming vs optimized multithreaded
277        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        // Determine the overall fastest approach
292        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}