use colored::Colorize;
use evalexpr_jit::system::EquationSystem;
use evalexpr_jit::Equation;
use fasteval::{Compiler, Evaler};
use std::collections::BTreeMap;
use std::time::Instant;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let n_runs = 100_000;
let n_equations = 500;
let expressions = generate_test_expressions(n_equations);
println!(
"\n{}",
"=== Fasteval Implementation ===".bright_blue().bold()
);
benchmark_fasteval(&expressions, n_runs)?;
println!(
"\n{}",
"=== EvalExpr Single Implementation ==="
.bright_green()
.bold()
);
benchmark_evalexpr_single(&expressions, n_runs)?;
println!(
"\n{}",
"=== EvalExpr JIT Implementation ===".bright_green().bold()
);
benchmark_evalexpr_system(&expressions, n_runs)?;
Ok(())
}
fn generate_test_expressions(n_equations: usize) -> Vec<String> {
(0..n_equations)
.map(|i| match i % 10 {
0 => format!("2*x + y^{}/z + sqrt(z^{}) + w*v", (i % 5) + 2, (i % 3) + 2),
1 => format!(
"exp(x/{}) + y^{}/sqrt(z) + ln(z+{}*x) + w/v",
(i % 4) + 2,
(i % 3) + 2,
(i % 3) + 1
),
2 => format!(
"sqrt(x^{}/y + y^{}/z + z^{}/x) + v*w",
(i % 3) + 2,
(i % 4) + 2,
(i % 5) + 2
),
3 => format!(
"(x+{}*y)^2 + (y+{}*z)^2 + (z+{}*x)^2 + w^2 + v^2",
(i % 3) + 1,
(i % 4) + 1,
(i % 5) + 1
),
4 => format!(
"ln(x+{}) + exp(y/{}) + z^{} + sqrt(w*v)",
(i % 4) + 2,
(i % 3) + 2,
(i % 5) + 2
),
5 => format!(
"(x*y)/(z+{}) + (y*z)/(x+{}) + (z*x)/(y+{}) + (w*v)/(x+1)",
(i % 3) + 1,
(i % 4) + 1,
(i % 5) + 1
),
6 => format!(
"sqrt(x^{}) + sqrt(y^{}) + ln(z^{}) + exp(w) + v^2",
(i % 3) + 2,
(i % 4) + 3,
(i % 5) + 4
),
7 => format!(
"(x^2 + {}*y^2)/(z + {}) + sqrt({}*x*y*z) + w*v",
(i % 3) + 1,
(i % 4) + 1,
(i % 5) + 1
),
8 => format!(
"(x^{} + y^{})/(z^{} + 1) + ln(w + v)",
(i % 4) + 2,
(i % 3) + 2,
(i % 5) + 2
),
9 => format!(
"sqrt(x^2 + {}*y^2)/(z^{} + ln(x + {})) + w/v",
(i % 5) + 1,
(i % 3) + 2,
(i % 4) + 1
),
_ => unreachable!(),
})
.collect()
}
fn benchmark_fasteval(expressions: &[String], n_runs: usize) -> Result<(), fasteval::Error> {
let parser = fasteval::Parser::new();
let mut slab = fasteval::Slab::new();
let mut map = BTreeMap::new();
let compiled_exprs: Vec<_> = expressions
.iter()
.map(|expr| {
parser
.parse(expr, &mut slab.ps)
.unwrap()
.from(&slab.ps)
.compile(&slab.ps, &mut slab.cs)
})
.collect();
map.insert("x", 1.0);
map.insert("y", 2.0);
map.insert("z", 3.0);
map.insert("w", 4.0);
map.insert("v", 5.0);
let start = Instant::now();
for _ in 0..n_runs {
for compiled in &compiled_exprs {
let _val = compiled.eval(&slab, &mut map);
}
}
let duration = start.elapsed();
println!(
"Time taken: {}",
format!(
"{:?} for {} runs and {} equations",
duration,
n_runs,
expressions.len()
)
.bright_yellow()
.italic()
);
let ns_per_eq =
(duration.as_secs_f64() * 1_000_000_000.0) / (n_runs as f64 * expressions.len() as f64);
println!(
"Average: {}",
format!("{ns_per_eq:.2}ns per equation")
.bright_cyan()
.bold()
);
Ok(())
}
fn benchmark_evalexpr_single(
expressions: &[String],
n_runs: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let start = Instant::now();
let equations: Vec<Equation> = expressions
.iter()
.map(|e| Equation::new(e.to_string()).unwrap())
.collect();
let duration_jit = start.elapsed();
let duration_str = format!("{:.3}s", duration_jit.as_secs_f64());
println!("JIT Compilation: {duration_str}");
let start = Instant::now();
for i in 0..n_runs {
let inputs = &[
(i + 1) as f64,
(i + 2) as f64,
(i + 3) as f64,
(i + 4) as f64,
(i + 5) as f64,
];
for eq in &equations {
let _val = eq.eval(inputs).unwrap();
}
}
let duration = start.elapsed();
println!(
"Single Sequential: {}",
format!(
"{:?} for {} runs and {} equations",
duration,
n_runs,
expressions.len()
)
.bright_yellow()
.italic()
);
let ns_per_eq =
(duration.as_secs_f64() * 1_000_000_000.0) / (n_runs as f64 * expressions.len() as f64);
println!(
"Single Average: {}",
format!("{ns_per_eq:.2}ns per equation")
.bright_cyan()
.bold()
);
Ok(())
}
fn benchmark_evalexpr_system(
expressions: &[String],
n_runs: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let start = Instant::now();
let system = EquationSystem::new(expressions.to_vec())?;
let duration_system = start.elapsed();
println!(
"JIT Compilation: {}",
format!("{:.3}s", duration_system.as_secs_f64())
.bright_yellow()
.italic()
);
let batch_input = (0..n_runs)
.map(|i| vec![i as f64, i as f64, i as f64, i as f64, i as f64])
.collect::<Vec<_>>();
let start = Instant::now();
let inputs = vec![(1) as f64, (2) as f64, (3) as f64, (4) as f64, (5) as f64];
for _ in 0..n_runs {
let _val = system.eval(&inputs).unwrap();
}
let duration_system = start.elapsed();
println!(
"System Sequential: {}",
format!(
"{:?} for {} runs and {} equations",
duration_system,
n_runs,
expressions.len()
)
.bright_yellow()
.italic()
);
let start = Instant::now();
system.eval_parallel(&batch_input).unwrap();
let duration_parallel = start.elapsed();
println!(
"System Parallel: {}",
format!(
"{:?} for {} runs and {} equations",
duration_parallel,
n_runs,
expressions.len()
)
.bright_yellow()
.italic()
);
let ns_per_eq_system = (duration_system.as_secs_f64() * 1_000_000_000.0)
/ (n_runs as f64 * expressions.len() as f64);
let ns_per_eq_parallel = (duration_parallel.as_secs_f64() * 1_000_000_000.0)
/ (n_runs as f64 * expressions.len() as f64);
println!(
"Sequential Average: {}",
format!("{ns_per_eq_system:.2}ns per equation")
.bright_cyan()
.bold()
);
println!(
"Parallel Average: {}",
format!("{ns_per_eq_parallel:.2}ns per equation")
.bright_magenta()
.bold()
);
Ok(())
}