use iterpipes::*; use time::Instant; mod piped { use iterpipes::Pipe; struct Envelope { attack_len: usize, decay_len: usize, } impl Envelope { pub fn new(attack_len: usize, decay_len: usize) -> Self { Self { attack_len, decay_len, } } } impl Pipe for Envelope { type InputItem = usize; type OutputItem = f32; fn next(&mut self, index: usize) -> f32 { if index < self.attack_len { index as f32 / self.attack_len as f32 } else if index < self.attack_len + self.decay_len { 1.0 - ((index - self.attack_len) as f32 / self.decay_len as f32) } else { 0.0 } } } struct SineWave { wave_length: usize, } impl SineWave { pub fn new(wave_length: usize) -> Self { SineWave { wave_length } } } impl Pipe for SineWave { type InputItem = usize; type OutputItem = f32; fn next(&mut self, index: usize) -> f32 { let index = index % self.wave_length; let progress = index as f32 / self.wave_length as f32; (progress * 2.0 * std::f32::consts::PI).sin() } } pub struct Metronome { env: Envelope, sine: SineWave, pulse_distance: usize, } impl Metronome { pub fn new( attack_len: usize, decay_len: usize, wave_length: usize, pulse_distance: usize, ) -> Self { Self { env: Envelope::new(attack_len, decay_len), sine: SineWave::new(wave_length), pulse_distance, } } } impl Pipe for Metronome { type InputItem = usize; type OutputItem = f32; fn next(&mut self, index: usize) -> f32 { self.sine.next(index) * self.env.next(index % self.pulse_distance) } } } mod manual { use iterpipes::Pipe; pub struct Metronome { attack_len: usize, decay_len: usize, wave_length: usize, pulse_distance: usize, } impl Metronome { pub fn new( attack_len: usize, decay_len: usize, wave_length: usize, pulse_distance: usize, ) -> Self { Self { attack_len, decay_len, wave_length, pulse_distance, } } } impl Pipe for Metronome { type InputItem = usize; type OutputItem = f32; fn next(&mut self, index: usize) -> f32 { let wave_index = index % self.wave_length; let wave_progress = wave_index as f32 / self.wave_length as f32; let wave_frame = (wave_progress * 2.0 * std::f32::consts::PI).sin(); let env_index = index % self.pulse_distance; let env_frame = if env_index < self.attack_len { index as f32 / self.attack_len as f32 } else if env_index < self.attack_len + self.decay_len { 1.0 - ((env_index - self.attack_len) as f32 / self.decay_len as f32) } else { 0.0 }; wave_frame * env_frame } } } fn benchmark_pipe(length: usize, runs: usize, mut factory: F) -> Vec where P: Pipe, F: FnMut() -> P, { use std::io::Write; let mut signal_buffer = vec![0.0; length].into_boxed_slice(); let mut durations: Vec = Vec::with_capacity(runs); for _ in 0..runs { let mut pipe = factory(); let start = Instant::now(); for i in 0..length { signal_buffer[i] = pipe.next(i); } let end = Instant::now(); let duration = (end - start).as_seconds_f32(); print!("{}, ", duration); std::io::stdout().flush().unwrap(); durations.push(duration); } println!(); durations } const INFO: &str = "# This program benchmarks pipes by rendering a simple metronome signal. # The signal is calculated by a pipes-based implementation first and by a manually implementated one # afterwards. Both implementations are executed 200 times each, which will take about 15 minutes, # depending on your system. The runtime of each execution is printed in a CSV-style format, which # can parsed and analyzed. # # This benchmark shows that pipes-based implementations is exactly as fast as a manual # implementation if it was compiled with the lto flag. # "; fn main() { const LEN: usize = 200_000_000; const RUNS: usize = 200; print!("{}", INFO); println!("# Runtimes of the piped version:"); let mut counter: usize = 0; let piped_durations: Vec = benchmark_pipe(LEN, RUNS, move || { counter += 1; piped::Metronome::new(500, 500, 100 * counter, 1_000) }); println!("# Runtimes of the manual version:"); let mut counter: usize = 0; let manual_durations: Vec = benchmark_pipe(LEN, RUNS, move || { counter += 1; manual::Metronome::new(500, 500, 100 * counter, 1_000) }); let mean_duration_piped: f32 = piped_durations.iter().sum::() / piped_durations.len() as f32; let mean_duration_manual: f32 = manual_durations.iter().sum::() / manual_durations.len() as f32; let combined: Vec<(f32, f32)> = Iterator::zip(piped_durations.iter(), manual_durations.iter()) .map(|(piped, manual)| (piped - manual, piped / manual)) .collect(); let ( mean_difference, mean_relation, min_difference, max_difference, min_relation, max_relation, ) = combined.iter().cloned().fold( ( 0.0, 0.0, combined[0].0, combined[0].0, combined[0].1, combined[0].1, ), |(diff_sum, rel_sum, min_diff, max_diff, min_rel, max_rel), (diff, rel)| { ( diff_sum + diff, rel_sum + rel, f32::min(min_diff, diff), f32::max(max_diff, diff), f32::min(min_rel, rel), f32::max(max_rel, rel), ) }, ); let (mean_difference, mean_relation) = ( mean_difference / combined.len() as f32, mean_relation / combined.len() as f32, ); println!( "# Mean duration of a piped execution: {}s", mean_duration_piped ); println!( "# Mean duration of a manual execution: {}s", mean_duration_manual ); println!( "# Minimal, Mean, and Maximal difference between a piped and a manual execution: {}s, {}s, {}s", min_difference, mean_difference, max_difference ); println!( "# Minimal, Mean, and Maximal relation between a piped and a manual exection: {}, {}, {}", min_relation, mean_relation, max_relation ); }