diff --git a/NAMESPACE b/NAMESPACE
index 54fb81a..c3eeb37 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -2,7 +2,5 @@
S3method("$<-",savvy_blazr__sealed)
S3method("[[<-",savvy_blazr__sealed)
-S3method(print,Person__bundle)
-export(int_times_int)
-export(to_upper)
+export(sum_with_threads)
useDynLib(blazr, .registration = TRUE)
diff --git a/R/000-wrappers.R b/R/000-wrappers.R
index 1cad530..3c12ace 100644
--- a/R/000-wrappers.R
+++ b/R/000-wrappers.R
@@ -37,67 +37,16 @@ NULL
stop(class, " cannot be modified", call. = FALSE)
}
-#' Convert Input To Upper-Case
+#' Calculate the sum of a vector of integers using multiple threads.
#'
-#' @param x A character vector.
-#' @returns A character vector with upper case version of the input.
-#' @export
-`to_upper` <- function(`x`) {
- .Call(savvy_to_upper__impl, `x`)
-}
-
-#' Multiply Input By Another Input
+#' @param x A vector of integers to sum over.
+#' @param n The number of threads used to compute this calculation (int).
+#'
+#' @return The sum of all elements of the input vector.
#'
-#' @param x An integer vector.
-#' @param y An integer to multiply.
-#' @returns An integer vector with values multiplied by `y`.
#' @export
-`int_times_int` <- function(`x`, `y`) {
- .Call(savvy_int_times_int__impl, `x`, `y`)
+`sum_with_threads` <- function(`x`, `n`) {
+ .Call(savvy_sum_with_threads__impl, `x`, `n`)
}
-### wrapper functions for Person
-
-`Person_set_name` <- function(self) {
- function(`name`) {
- invisible(.Call(savvy_Person_set_name__impl, `self`, `name`))
- }
-}
-
-`Person_name` <- function(self) {
- function() {
- .Call(savvy_Person_name__impl, `self`)
- }
-}
-
-`.savvy_wrap_Person` <- function(ptr) {
- e <- new.env(parent = emptyenv())
- e$.ptr <- ptr
- e$`set_name` <- `Person_set_name`(ptr)
- e$`name` <- `Person_name`(ptr)
-
- class(e) <- c("Person", "savvy_blazr__sealed")
- e
-}
-
-
-`Person` <- new.env(parent = emptyenv())
-
-### associated functions for Person
-
-`Person`$`new` <- function() {
- .savvy_wrap_Person(.Call(savvy_Person_new__impl))
-}
-
-`Person`$`associated_function` <- function() {
- .Call(savvy_Person_associated_function__impl)
-}
-
-
-class(`Person`) <- c("Person__bundle", "savvy_blazr__sealed")
-
-#' @export
-`print.Person__bundle` <- function(x, ...) {
- cat('Person')
-}
diff --git a/README.Rmd b/README.Rmd
index 32224cf..b1ea87c 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -36,10 +36,53 @@ pak::pak("r-staceans/blazr")
## Example
-This is just a dummy example:
+Here's a simple example, computing the sum of a vector of integers, using multiple threads:
```{r example}
library(blazr)
-blazr::to_upper("hello, world")
+create_int_vector <- function(n) {
+ set.seed(42)
+ sample.int(100, n, replace = TRUE)
+}
+
+n <- 1e8
+
+x <- create_int_vector(n)
+
+blazr::sum_with_threads(
+ x,
+ n = 2L
+)
+```
+
+## Benchmarking
+
+When running this sum against very large numbers, we can see the performance benefits of using multiple threads:
+
+```{r benchmark}
+library(bench)
+library(ggplot2)
+
+results <- bench::press(
+ size = c(1e7, 1e8, 1e9),
+ {
+ x = create_int_vector(n)
+ bench::mark(
+ single_thread = {
+ blazr::sum_with_threads(
+ x,
+ n = 1L
+ )
+ },
+ multi_thread = {
+ blazr::sum_with_threads(
+ x,
+ n = 4L
+ )
+ }
+ )
+ })
+
+ggplot2::autoplot(results)
```
diff --git a/README.md b/README.md
index 3c430a0..f81905f 100644
--- a/README.md
+++ b/README.md
@@ -28,11 +28,64 @@ pak::pak("r-staceans/blazr")
## Example
-This is just a dummy example:
+Here’s a simple example, computing the sum of a vector of integers,
+using multiple threads:
``` r
library(blazr)
-blazr::to_upper("hello, world")
-#> [1] "HELLO, WORLD"
+create_int_vector <- function(n) {
+ set.seed(42)
+ sample.int(100, n, replace = TRUE)
+}
+
+n <- 1e8
+
+x <- create_int_vector(n)
+
+blazr::sum_with_threads(
+ x,
+ n = 2L
+)
+#> [1] 754832969
```
+
+## Benchmarking
+
+When running this sum against very large numbers, we can see the
+performance benefits of using multiple threads:
+
+``` r
+library(bench)
+library(ggplot2)
+
+results <- bench::press(
+ size = c(1e7, 1e8, 1e9),
+ {
+ x = create_int_vector(n)
+ bench::mark(
+ single_thread = {
+ blazr::sum_with_threads(
+ x,
+ n = 1L
+ )
+ },
+ multi_thread = {
+ blazr::sum_with_threads(
+ x,
+ n = 4L
+ )
+ }
+ )
+ })
+#> Running with:
+#> size
+#> 1 10000000
+#> 2 100000000
+#> 3 1000000000
+
+ggplot2::autoplot(results)
+#> Loading required namespace: tidyr
+```
+
+
diff --git a/man/blazr-package.Rd b/man/blazr-package.Rd
index d20cc1e..48f2e16 100644
--- a/man/blazr-package.Rd
+++ b/man/blazr-package.Rd
@@ -6,6 +6,8 @@
\alias{blazr-package}
\title{blazr: What the Package Does (One Line, Title Case)}
\description{
+\if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}}
+
What the package does (one paragraph).
}
\seealso{
diff --git a/man/figures/README-benchmark-1.png b/man/figures/README-benchmark-1.png
new file mode 100644
index 0000000..fd3b1bb
Binary files /dev/null and b/man/figures/README-benchmark-1.png differ
diff --git a/man/int_times_int.Rd b/man/int_times_int.Rd
deleted file mode 100644
index 565baee..0000000
--- a/man/int_times_int.Rd
+++ /dev/null
@@ -1,19 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/000-wrappers.R
-\name{int_times_int}
-\alias{int_times_int}
-\title{Multiply Input By Another Input}
-\usage{
-int_times_int(x, y)
-}
-\arguments{
-\item{x}{An integer vector.}
-
-\item{y}{An integer to multiply.}
-}
-\value{
-An integer vector with values multiplied by \code{y}.
-}
-\description{
-Multiply Input By Another Input
-}
diff --git a/man/sum_with_threads.Rd b/man/sum_with_threads.Rd
new file mode 100644
index 0000000..06fbfbc
--- /dev/null
+++ b/man/sum_with_threads.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/000-wrappers.R
+\name{sum_with_threads}
+\alias{sum_with_threads}
+\title{Calculate the sum of a vector of integers using multiple threads.}
+\usage{
+sum_with_threads(x, n)
+}
+\arguments{
+\item{x}{A vector of integers to sum over.}
+
+\item{n}{The number of threads used to compute this calculation (int).}
+}
+\value{
+The sum of all elements of the input vector.
+}
+\description{
+Calculate the sum of a vector of integers using multiple threads.
+}
diff --git a/man/to_upper.Rd b/man/to_upper.Rd
deleted file mode 100644
index e2d9321..0000000
--- a/man/to_upper.Rd
+++ /dev/null
@@ -1,17 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/000-wrappers.R
-\name{to_upper}
-\alias{to_upper}
-\title{Convert Input To Upper-Case}
-\usage{
-to_upper(x)
-}
-\arguments{
-\item{x}{A character vector.}
-}
-\value{
-A character vector with upper case version of the input.
-}
-\description{
-Convert Input To Upper-Case
-}
diff --git a/src/init.c b/src/init.c
index ef313b6..2e0293f 100644
--- a/src/init.c
+++ b/src/init.c
@@ -34,44 +34,14 @@ SEXP handle_result(SEXP res_) {
return (SEXP)res;
}
-SEXP savvy_to_upper__impl(SEXP c_arg__x) {
- SEXP res = savvy_to_upper__ffi(c_arg__x);
- return handle_result(res);
-}
-
-SEXP savvy_int_times_int__impl(SEXP c_arg__x, SEXP c_arg__y) {
- SEXP res = savvy_int_times_int__ffi(c_arg__x, c_arg__y);
- return handle_result(res);
-}
-
-SEXP savvy_Person_new__impl(void) {
- SEXP res = savvy_Person_new__ffi();
- return handle_result(res);
-}
-
-SEXP savvy_Person_set_name__impl(SEXP self__, SEXP c_arg__name) {
- SEXP res = savvy_Person_set_name__ffi(self__, c_arg__name);
- return handle_result(res);
-}
-
-SEXP savvy_Person_name__impl(SEXP self__) {
- SEXP res = savvy_Person_name__ffi(self__);
- return handle_result(res);
-}
-
-SEXP savvy_Person_associated_function__impl(void) {
- SEXP res = savvy_Person_associated_function__ffi();
+SEXP savvy_sum_with_threads__impl(SEXP c_arg__x, SEXP c_arg__n) {
+ SEXP res = savvy_sum_with_threads__ffi(c_arg__x, c_arg__n);
return handle_result(res);
}
static const R_CallMethodDef CallEntries[] = {
- {"savvy_to_upper__impl", (DL_FUNC) &savvy_to_upper__impl, 1},
- {"savvy_int_times_int__impl", (DL_FUNC) &savvy_int_times_int__impl, 2},
- {"savvy_Person_new__impl", (DL_FUNC) &savvy_Person_new__impl, 0},
- {"savvy_Person_set_name__impl", (DL_FUNC) &savvy_Person_set_name__impl, 2},
- {"savvy_Person_name__impl", (DL_FUNC) &savvy_Person_name__impl, 1},
- {"savvy_Person_associated_function__impl", (DL_FUNC) &savvy_Person_associated_function__impl, 0},
+ {"savvy_sum_with_threads__impl", (DL_FUNC) &savvy_sum_with_threads__impl, 2},
{NULL, NULL, 0}
};
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 96fe1ab..d08356c 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -1,6 +1,13 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
+
+[[package]]
+name = "blazr"
+version = "0.1.0"
+dependencies = [
+ "savvy",
+]
[[package]]
name = "cc"
@@ -11,13 +18,6 @@ dependencies = [
"shlex",
]
-[[package]]
-name = "blazr"
-version = "0.1.0"
-dependencies = [
- "savvy",
-]
-
[[package]]
name = "once_cell"
version = "1.20.2"
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 2986339..2b6984b 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -9,6 +9,9 @@ crate-type = ["staticlib", "lib"]
[dependencies]
savvy = "*"
+[features]
+savvy-test = []
+
[profile.release]
# By default, on release build, savvy terminates the R session when a panic
# occurs. This is the right behavior in that a panic means such a fatal event
diff --git a/src/rust/api.h b/src/rust/api.h
index c5f3fd1..e3ea717 100644
--- a/src/rust/api.h
+++ b/src/rust/api.h
@@ -1,8 +1 @@
-SEXP savvy_to_upper__ffi(SEXP c_arg__x);
-SEXP savvy_int_times_int__ffi(SEXP c_arg__x, SEXP c_arg__y);
-
-// methods and associated functions for Person
-SEXP savvy_Person_new__ffi(void);
-SEXP savvy_Person_set_name__ffi(SEXP self__, SEXP c_arg__name);
-SEXP savvy_Person_name__ffi(SEXP self__);
-SEXP savvy_Person_associated_function__ffi(void);
\ No newline at end of file
+SEXP savvy_sum_with_threads__ffi(SEXP c_arg__x, SEXP c_arg__n);
diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs
index 9b94e3e..280897a 100644
--- a/src/rust/src/lib.rs
+++ b/src/rust/src/lib.rs
@@ -1,117 +1,106 @@
-// Example functions
+use savvy::{savvy, IntegerSexp, Sexp};
+use std::thread;
-use savvy::savvy;
-
-use savvy::{IntegerSexp, OwnedIntegerSexp, OwnedStringSexp, StringSexp};
-
-use savvy::NotAvailableValue;
-
-/// Convert Input To Upper-Case
+/// Calculate the sum of a vector of integers using multiple threads.
+///
+/// @param x A vector of integers to sum over.
+/// @param n The number of threads used to compute this calculation (int).
+///
+/// @return The sum of all elements of the input vector.
///
-/// @param x A character vector.
-/// @returns A character vector with upper case version of the input.
/// @export
#[savvy]
-fn to_upper(x: StringSexp) -> savvy::Result {
- let mut out = OwnedStringSexp::new(x.len())?;
+fn sum_with_threads(x: IntegerSexp, n: i32) -> savvy::Result {
+ let x_rust = x.to_vec();
+ let n_usize: usize = n as usize;
- for (i, e) in x.iter().enumerate() {
- if e.is_na() {
- out.set_na(i)?;
- continue;
- }
+ let out = sum_with_threads_impl(x_rust, n_usize);
+ out.try_into()
+}
- let e_upper = e.to_uppercase();
- out.set_elt(i, &e_upper)?;
+fn sum_with_threads_impl(x: Vec, n: usize) -> i32 {
+ if x.is_empty() {
+ eprintln!("Input vector is empty. Returning 0.");
+ return 0;
}
- Ok(out.into())
-}
+ let n = n.min(x.len());
+ let chunk_size = (x.len() + n - 1) / n;
-/// Multiply Input By Another Input
-///
-/// @param x An integer vector.
-/// @param y An integer to multiply.
-/// @returns An integer vector with values multiplied by `y`.
-/// @export
-#[savvy]
-fn int_times_int(x: IntegerSexp, y: i32) -> savvy::Result {
- let mut out = OwnedIntegerSexp::new(x.len())?;
-
- for (i, e) in x.iter().enumerate() {
- if e.is_na() {
- out.set_na(i)?;
- } else {
- out[i] = e * y;
- }
+ let mut handles = Vec::new();
+ for i in 0..n {
+ let chunk = x[i * chunk_size..((i + 1) * chunk_size).min(x.len())].to_vec();
+ handles.push(thread::spawn(move || chunk.iter().sum::()));
}
- Ok(out.into())
-}
+ let mut total_sum = 0;
+ for handle in handles {
+ total_sum += handle.join().expect("Thread panicked");
+ }
-#[savvy]
-struct Person {
- pub name: String,
+ total_sum
}
-/// A person with a name
-///
-/// @export
-#[savvy]
-impl Person {
- fn new() -> Self {
- Self {
- name: "".to_string(),
- }
- }
+#[cfg(test)]
+mod tests {
+ use crate::sum_with_threads_impl;
- fn set_name(&mut self, name: &str) -> savvy::Result<()> {
- self.name = name.to_string();
- Ok(())
+ #[test]
+ fn test_single_thread() {
+ let numbers = vec![1, 2, 3, 4, 5];
+ let n = 1;
+ assert_eq!(sum_with_threads_impl(numbers, n), 15);
}
- fn name(&self) -> savvy::Result {
- let mut out = OwnedStringSexp::new(1)?;
- out.set_elt(0, &self.name)?;
- Ok(out.into())
+ #[test]
+ fn test_multiple_threads() {
+ let x = vec![1, 2, 3, 4];
+ let num_threads = 2;
+
+ let result = sum_with_threads_impl(x, num_threads);
+ assert_eq!(result, 10);
}
- fn associated_function() -> savvy::Result {
- let mut out = OwnedStringSexp::new(1)?;
- out.set_elt(0, "associated_function")?;
- Ok(out.into())
+ #[test]
+ fn test_more_threads_than_elements() {
+ let numbers = vec![1, 2, 3, 4, 5];
+ let n = 10;
+ assert_eq!(sum_with_threads_impl(numbers, n), 15);
}
-}
-// This test is run by `cargo test`. You can put tests that don't need a real
-// R session here.
-#[cfg(test)]
-mod test1 {
#[test]
- fn test_person() {
- let mut p = super::Person::new();
- p.set_name("foo").expect("set_name() must succeed");
- assert_eq!(&p.name, "foo");
+ fn test_empty_vector() {
+ let numbers: Vec = vec![];
+ let n = 4;
+ assert_eq!(sum_with_threads_impl(numbers, n), 0);
}
-}
-// Tests marked under `#[cfg(feature = "savvy-test")]` are run by `savvy-cli test`, which
-// executes the Rust code on a real R session so that you can use R things for
-// testing.
-#[cfg(feature = "savvy-test")]
-mod test1 {
- // The return type must be `savvy::Result<()>`
#[test]
- fn test_to_upper() -> savvy::Result<()> {
- // You can create a non-owned version of input by `.as_read_only()`
- let x = savvy::OwnedStringSexp::try_from_slice(["foo", "bar"])?.as_read_only();
+ fn test_large_numbers() {
+ let numbers = vec![1_000_000, 2_000_000, 3_000_000];
+ let n = 3;
+ assert_eq!(sum_with_threads_impl(numbers, n), 6_000_000);
+ }
- let result = super::to_upper(x)?;
+ #[test]
+ fn test_negative_numbers() {
+ let numbers = vec![-1, -2, -3, -4, -5];
+ let n = 2;
+ assert_eq!(sum_with_threads_impl(numbers, n), -15);
+ }
- // This function compares an SEXP with the result of R code specified in
- // the second argument.
- savvy::assert_eq_r_code(result, r#"c("FOO", "BAR")"#);
+ #[test]
+ fn test_mixed_numbers() {
+ let numbers = vec![-1, 2, -3, 4, -5, 6];
+ let n = 3;
+ assert_eq!(sum_with_threads_impl(numbers, n), 3);
+ }
- Ok(())
+ #[test]
+ fn test_large_vector() {
+ let numbers: Vec = (1..=1_000).collect();
+ let n = 4;
+ let expected_sum: i32 = (1..=1_000).sum();
+ assert_eq!(sum_with_threads_impl(numbers, n), expected_sum);
}
}
diff --git a/tests/testthat/test-add.R b/tests/testthat/test-add.R
deleted file mode 100644
index ac8727f..0000000
--- a/tests/testthat/test-add.R
+++ /dev/null
@@ -1,3 +0,0 @@
-test_that("addition works", {
- expect_equal(add(2, 2), 4)
-})
diff --git a/tests/testthat/test-sum_with_threads.R b/tests/testthat/test-sum_with_threads.R
new file mode 100644
index 0000000..922192d
--- /dev/null
+++ b/tests/testthat/test-sum_with_threads.R
@@ -0,0 +1,6 @@
+test_that("sums as expected", {
+ vector <- 1:10
+
+ out <- blazr::sum_with_threads(1:10, 5L)
+ expect_equal(out, sum(vector))
+})