In a previous post, I called a Rust function from R and did a speed test of that function against an equivalent R function. At the end of the post, I mentioned that we could use memoization to speed up the R function. I proceed to do that here.
The speed test in the previous post might have been unfair to R, since it is well known that R is not good with recursive function calls. I now create a memoized version of the R function here:
library(rextendr)
library(ggplot2)
library(microbenchmark)
# write memoized r function
fibonacci <- function(n) {
fib <- vector('numeric', n)
for(ii in 1:(n+1)) {
# R vector indices start at 1
if(ii==1) {
fib[ii] <- 0
} else if(ii==2) {
fib[ii] <- 1
} else {
fib[ii] <- fib[ii-1] + fib[ii-2]
}
}
return(fib[n+1])
}
# run the R function
r_fn_result = fibonacci(15)
print(r_fn_result)
## [1] 610
# test run time for a larger term number
system.time(fibonacci(40))
## user system elapsed
## 0 0 0
Note that the function returns the correct value for the \(15^{th}\) term and is much faster now for generating the value of the 40th Fibonacci term (it’s even faster than the Rust function, which uses recursive function calls – see the previous post)
I now write a memoized version of the Rust function, and call it in R:
# write memoized Rust function
rust_memoized_code <- "
#[extendr]
fn fibonacci_rust(n:usize) -> i64 {
let mut fibn: Vec<i64> = vec![0;(n+1)];
for ii in 0..=n {
if ii==0 {
fibn[ii] = 0;
} else if ii==1 {
fibn[ii] = 1;
} else {
fibn[ii] = fibn[ii-1] + fibn[ii-2];
}
}
return fibn[n];
}"
# compile source code
rust_source(code = rust_memoized_code, quiet=TRUE)
# run the rust function
rustm_fn_result = fibonacci_rust(15)
print(rustm_fn_result)
## [1] 610
Which, again is the correct value for the \(15^{th}\) term. I now do a microbenchmark test:
# use microbenchmark to compare runtimes
# list of test values
values <- c(40, 50, 60)
# list to store microbenchmark results
compare <- vector('list', length(values))
for (ii in 1:length(values)) {
compare[[ii]] <- microbenchmark(fibonacci(values[ii]), fibonacci_rust(values[ii]), times = 1000)
# Change labels for plotting convenience later
# Convert expressions to character strings
expr_char <- as.character(compare[[ii]]$expr)
# Map to new labels
expr_char[expr_char == "fibonacci(values[ii])"] <- "R"
expr_char[expr_char == "fibonacci_rust(values[ii])"] <- "Rust"
# Assign the new factor with correct levels
compare[[ii]]$expr <- factor(expr_char, levels = c("R", "Rust"))
}
# plot benchmark times, term number = 40
autoplot(compare[[1]]) +
ggtitle(sprintf("Rust vs R: Microbenchmark Results for Term Number: %d", values[1]))

# plot benchmark times, term number = 50
autoplot(compare[[2]]) +
ggtitle(sprintf("Rust vs R: Microbenchmark Results for Term Number: %d", values[2]))

# plot benchmark times, term number = 60
autoplot(compare[[3]]) +
ggtitle(sprintf("Rust vs R: Microbenchmark Results for Term Number: %d", values[3]))

Note that while the R function appears to be faster when n=40, the Rust function tends to do better speed wise when n gets larger. That said, the Rust function appears to have instances where it runs much longer than the R function.