Use bench tests to optimize factorial and fibonacci functions
timw4mail/rusty-numbers/pipeline/head This commit looks good Details

This commit is contained in:
Timothy Warren 2020-02-20 21:26:28 -05:00
parent 75fe940ffc
commit 3624b415ee
2 changed files with 90 additions and 73 deletions

View File

@ -1,40 +1,32 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use rusty_numbers::{factorial, fibonacci, rec_factorial, rec_fibonacci}; use rusty_numbers::{factorial, fibonacci, mem_fibonacci, mem_factorial};
fn bench_factorial(c: &mut Criterion) { fn bench_factorial(c: &mut Criterion) {
let mut group = c.benchmark_group("Factorials"); let mut group = c.benchmark_group("Factorials");
// Recursive with lookup table for i in [0usize, 5, 10, 15, 20, 25, 30, 34].iter() {
group.bench_function("factorial", |b| b.iter(|| factorial(34))); group.bench_with_input(BenchmarkId::new("Recursive memoized", i), i, |b, i| {
group.bench_function("factorial black box", |b| { b.iter(|| mem_factorial(*i))
b.iter(|| factorial(black_box(34))) });
}); group.bench_with_input(BenchmarkId::new("Recursive naive", i), i, |b, i| {
b.iter(|| factorial(*i))
// Naive recursive });
group.bench_function("rec_factorial", |b| b.iter(|| rec_factorial(34))); }
group.bench_function("rec_factorial black box", |b| {
b.iter(|| rec_factorial(black_box(34)))
});
group.finish(); group.finish();
} }
fn bench_fibonacci(c: &mut Criterion) { fn bench_fibonacci(c: &mut Criterion) {
let mut group = c.benchmark_group("Fibonacci"); let mut group = c.benchmark_group("Fibonacci");
// Recursive with lookup table for i in [0usize, 5, 10, 15, 20, 30].iter() {
group.bench_function("fibonacci", |b| b.iter(|| fibonacci(86))); group.bench_with_input(BenchmarkId::new("Recursive memoized", i), i, |b, i| {
group.bench_function("fibonacci black box", |b| { b.iter(|| mem_fibonacci(*i))
b.iter(|| fibonacci(black_box(86))) });
}); // group.bench_with_input(BenchmarkId::new("Recursive naive", i), i, |b, i| b.iter(|| rec_fibonacci(*i)));
group.bench_with_input(BenchmarkId::new("Iterative", i), i, |b, i| {
// Naive recursive b.iter(|| fibonacci(*i))
group.bench_function("rec_fibonacci", |b| b.iter(|| rec_fibonacci(86))); });
group.bench_function("rec_fibonacci black box", |b| { }
b.iter(|| rec_fibonacci(black_box(86)))
});
group.finish();
} }
criterion_group!(benches, bench_factorial, bench_fibonacci); criterion_group!(benches, bench_factorial, bench_fibonacci);

View File

@ -25,36 +25,42 @@ pub mod rational;
/// # assert!(invalid.is_none()); /// # assert!(invalid.is_none());
/// ``` /// ```
#[inline] #[inline]
pub fn fibonacci(n: usize) -> Option<u128> { pub fn mem_fibonacci(n: usize) -> Option<u128> {
let mut table: Vec<u128> = vec![0, 1, 1, 2, 3, 5]; let mut table = [0u128; 187];
table[0] = 0;
table[1] = 1;
table[2] = 1;
_fibonacci(n, &mut table) _mem_fibonacci(n, &mut table)
} }
/// Actual calculating function for `fibonacci` /// Actual calculating function for `fibonacci`
#[inline] #[inline]
fn _fibonacci(n: usize, table: &mut Vec<u128>) -> Option<u128> { fn _mem_fibonacci(n: usize, table: &mut [u128]) -> Option<u128> {
match table.get(n) { if n > 186 {
Some(x) => Some(*x), return None;
None => { }
let a = _fibonacci(n - 1, table)?;
let b = _fibonacci(n - 2, table)?;
// Check for overflow when adding match table[n] {
let attempt = a.checked_add(b); // The lookup array starts out zeroed, so a zero
// is a not yet calculated value
0 => {
let a = _mem_fibonacci(n - 1, table)?;
let b = _mem_fibonacci(n - 2, table)?;
if let Some(current) = attempt { table[n] = a + b;
table.insert(n, current);
}
attempt Some(table[n])
} },
x => Some(x)
} }
} }
/// Calculate a number in the fibonacci sequence, /// Calculate a number in the fibonacci sequence,
/// using naive recursion /// using naive recursion
/// ///
/// REALLY SLOW
///
/// Can calculate up to 186 using native unsigned 128 bit integers. /// Can calculate up to 186 using native unsigned 128 bit integers.
#[inline] #[inline]
pub fn rec_fibonacci(n: usize) -> Option<u128> { pub fn rec_fibonacci(n: usize) -> Option<u128> {
@ -66,20 +72,24 @@ pub fn rec_fibonacci(n: usize) -> Option<u128> {
let b = rec_fibonacci(n - 2)?; let b = rec_fibonacci(n - 2)?;
a.checked_add(b) a.checked_add(b)
}, }
} }
} }
/// Calculate a number in the fibonacci sequence,
/// using iteration
///
/// Can calculate up to 186
#[inline] #[inline]
pub fn it_fibonacci(n: usize) -> Option<u128> { pub fn fibonacci(n: usize) -> Option<u128> {
let mut a:u128 = 0; let mut a: u128 = 0;
let mut b:u128 = 1; let mut b: u128 = 1;
match n { match n {
0 => Some(a), 0 => Some(a),
1 => Some(b), 1 => Some(b),
_ => { _ => {
for _ in 0..n-1 { for _ in 0..n - 1 {
let c: u128 = a.checked_add(b)?; let c: u128 = a.checked_add(b)?;
a = b; a = b;
@ -107,49 +117,60 @@ pub fn it_fibonacci(n: usize) -> Option<u128> {
/// # assert!(invalid.is_none()); /// # assert!(invalid.is_none());
/// ``` /// ```
#[inline] #[inline]
pub fn factorial(n: usize) -> Option<u128> { pub fn mem_factorial(n: usize) -> Option<u128> {
let mut table: Vec<u128> = vec![1, 1, 2]; let mut table = [0u128; 35];
table[0] = 1;
table[1] = 1;
table[2] = 2;
_factorial(n, &mut table)
_mem_factorial(n, &mut table)
} }
/// Actual Calculation function for factoral /// Actual Calculation function for factoral
#[inline] #[inline]
fn _factorial(n: usize, table: &mut Vec<u128>) -> Option<u128> { fn _mem_factorial(n: usize, table: &mut [u128]) -> Option<u128> {
match table.get(n) { if n > 34 {
// Vec<T>.get returns a Option with a reference to the value, return None
// so deref and wrap in Some() for proper return type }
Some(x) => Some(*x),
None => { match table[n] {
// Empty spaces in the lookup array are zero-filled
0 => {
// The ? suffix passes along the Option value // The ? suffix passes along the Option value
// to be handled later // to be handled later
// See: https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator // See: https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator
let prev = _factorial(n - 1, table)?; let prev = _mem_factorial(n - 1, table)?;
// Do an overflow-checked multiply table[n] = (n as u128) * prev;
let attempt = (n as u128).checked_mul(prev);
// If there isn't an overflow, add the result Some(table[n])
// to the calculation table },
if let Some(current) = attempt { x => Some(x),
table.insert(n, current);
}
attempt // Some(x) if no overflow
}
} }
} }
/// Calculate the value of a factorial using recursion /// Calculate the value of a factorial,
/// ///
/// Can calculate up to 34! using native unsigned 128 bit integers. /// Can calculate up to 34! using native unsigned 128 bit integers.
///
/// Example:
/// ```rust
/// use rusty_numbers::factorial;
///
/// let valid = factorial(3); // Some(6)
/// # assert_eq!(6, valid.unwrap());
///
/// let invalid = factorial(35); // None
/// # assert!(invalid.is_none());
/// ```
#[inline] #[inline]
pub fn rec_factorial(n: usize) -> Option<u128> { pub fn factorial(n: usize) -> Option<u128> {
match n { match n {
0 => Some(1u128), 0 => Some(1u128),
1 => Some(1u128), 1 => Some(1u128),
_ => { _ => {
let prev = rec_factorial(n - 1)?; let prev = factorial(n - 1)?;
(n as u128).checked_mul(prev) (n as u128).checked_mul(prev)
} }
@ -168,7 +189,7 @@ mod tests {
assert_eq!(6, factorial(3).unwrap()); assert_eq!(6, factorial(3).unwrap());
let res = factorial(34); let res = factorial(34);
let res2 = rec_factorial(34); let res2 = mem_factorial(34);
assert!(res.is_some()); assert!(res.is_some());
assert_eq!(res.unwrap(), res2.unwrap()); assert_eq!(res.unwrap(), res2.unwrap());
@ -182,8 +203,12 @@ mod tests {
assert_eq!(1, fibonacci(1).unwrap()); assert_eq!(1, fibonacci(1).unwrap());
assert_eq!(1, fibonacci(2).unwrap()); assert_eq!(1, fibonacci(2).unwrap());
let res = fibonacci(20);
let res2 = rec_fibonacci(20);
assert_eq!(res, res2);
let res = fibonacci(186); let res = fibonacci(186);
let res2 = it_fibonacci(186); let res2 = mem_fibonacci(186);
assert!(res.is_some()); assert!(res.is_some());
assert!(res2.is_some()); assert!(res2.is_some());
assert_eq!(res.unwrap(), res2.unwrap()); assert_eq!(res.unwrap(), res2.unwrap());