Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
SystemRequirements: Cargo (Rust's package manager), rustc
Imports:
vctrs
10 changes: 4 additions & 6 deletions R/000-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@ NULL
stop(class, " cannot be modified", call. = FALSE)
}

#' Calculate the sum of a vector of integers using multiple threads.
#' Calculate the sum of a vector of real numbers using multiple threads.
#'
#' @param x A vector of integers to sum over.
#' @param x A vector of real numbers 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.
#'
#' @export
`sum_with_threads` <- function(`x`, `n`) {
.Call(savvy_sum_with_threads__impl, `x`, `n`)
`sum_with_threads_real` <- function(`x`, `n`) {
.Call(savvy_sum_with_threads_real__impl, `x`, `n`)
}


3 changes: 0 additions & 3 deletions R/add.R

This file was deleted.

28 changes: 28 additions & 0 deletions R/sum_with_threads.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#' Calculate the sum of a vector of numbers using multiple threads.
#'
#' @param x A vector of numbers 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.
#' @export
#'
#' @examples
#' sum_with_threads(c(1,2), 2)
sum_with_threads <- function(x, n) {

tryCatch(
x <- vctrs::vec_cast(x, double()),
error = function(e) {
stop("x must be coercible to a numeric vector")
}
)

tryCatch(
n <- vctrs::vec_cast(n, integer()),
error = function(e) {
stop("n must be coercible to an integer")
}
)

sum_with_threads_real(x, n)
}
1 change: 1 addition & 0 deletions blazr.Rproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Version: 1.0
ProjectId: b96439b3-4184-410b-ad08-b3d443f15d8e

RestoreWorkspace: No
SaveWorkspace: No
Expand Down
11 changes: 7 additions & 4 deletions man/sum_with_threads.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions man/sum_with_threads_real.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ SEXP handle_result(SEXP res_) {
return (SEXP)res;
}

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);
SEXP savvy_sum_with_threads_real__impl(SEXP c_arg__x, SEXP c_arg__n) {
SEXP res = savvy_sum_with_threads_real__ffi(c_arg__x, c_arg__n);
return handle_result(res);
}


static const R_CallMethodDef CallEntries[] = {
{"savvy_sum_with_threads__impl", (DL_FUNC) &savvy_sum_with_threads__impl, 2},
{"savvy_sum_with_threads_real__impl", (DL_FUNC) &savvy_sum_with_threads_real__impl, 2},
{NULL, NULL, 0}
};

Expand Down
2 changes: 1 addition & 1 deletion src/rust/api.h
Original file line number Diff line number Diff line change
@@ -1 +1 @@
SEXP savvy_sum_with_threads__ffi(SEXP c_arg__x, SEXP c_arg__n);
SEXP savvy_sum_with_threads_real__ffi(SEXP c_arg__x, SEXP c_arg__n);
170 changes: 35 additions & 135 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,22 @@
use savvy::{savvy, NumericSexp, NumericTypedSexp, IntegerSexp, RealSexp, Sexp};
use savvy::{savvy, RealSexp};
use std::thread;

/// Calculate the sum of a vector of integers/doubles using multiple threads.
/// Calculate the sum of a vector of real numbers using multiple threads.
///
/// @param x A vector of integers/doubles to sum over.
/// @param x A vector of real numbers 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.
///
/// @export
#[savvy]
fn sum_with_threads(x: NumericSexp, n: i32) -> savvy::Result<savvy::Sexp> {
match x.into_typed() {
NumericTypedSexp::Integer(i) => sum_with_threads_int(i, n),
NumericTypedSexp::Real(r) => sum_with_threads_rel(r, n),
}
}

fn sum_with_threads_int(x: IntegerSexp, n: i32) -> savvy::Result<Sexp> {
let x_rust = x.to_vec();
let n_usize: usize = n as usize;

let out = sum_with_threads_int_impl(x_rust, n_usize);
out.try_into()
}

fn sum_with_threads_rel(x: RealSexp, n: i32) -> savvy::Result<Sexp> {
fn sum_with_threads_real(x: RealSexp, n: i32) -> savvy::Result<savvy::Sexp> {
let x_rust = x.to_vec();
let n_usize: usize = n as usize;
let n_size: usize = n as usize;

let out = sum_with_threads_rel_impl(x_rust, n_usize);
let out = sum_with_threads_real_impl(x_rust, n_size);
out.try_into()
}

fn sum_with_threads_int_impl(x: Vec<i32>, n: usize) -> i32 {
if x.is_empty() {
eprintln!("Input vector is empty. Returning 0.");
return 0;
}

let n = n.min(x.len());
let chunk_size = (x.len() + n - 1) / n;

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::<i32>()));
}

let mut total_sum = 0;
for handle in handles {
total_sum += handle.join().expect("Thread panicked");
}

total_sum
}

fn sum_with_threads_rel_impl(x: Vec<f64>, n: usize) -> f64 {
fn sum_with_threads_real_impl(x: Vec<f64>, n: usize) -> f64 {
if x.is_empty() {
eprintln!("Input vector is empty. Returning 0.");
return 0.0;
Expand All @@ -81,125 +41,65 @@ fn sum_with_threads_rel_impl(x: Vec<f64>, n: usize) -> f64 {

#[cfg(test)]
mod tests {
use crate::{sum_with_threads_int_impl, sum_with_threads_rel_impl};
use crate::sum_with_threads_real_impl;

/// Integers
/// Real/doubles
#[test]
fn test_single_thread_int() {
let numbers = vec![1, 2, 3, 4, 5];
fn test_single_thread_rel() {
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let n = 1;
assert_eq!(sum_with_threads_int_impl(numbers, n), 15);
assert_eq!(sum_with_threads_real_impl(numbers, n), 15.0);
}

#[test]
fn test_multiple_threads_int() {
let x = vec![1, 2, 3, 4];
fn test_multiple_threads_rel() {
let x = vec![1.0, 2.0, 3.0, 4.0];
let num_threads = 2;

let result = sum_with_threads_int_impl(x, num_threads);
assert_eq!(result, 10);
let result = sum_with_threads_real_impl(x, num_threads);
assert_eq!(result, 10.0);
}

#[test]
fn test_more_threads_than_elements_int() {
let numbers = vec![1, 2, 3, 4, 5];
fn test_more_threads_than_elements_rel() {
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let n = 10;
assert_eq!(sum_with_threads_int_impl(numbers, n), 15);
assert_eq!(sum_with_threads_real_impl(numbers, n), 15.0);
}

#[test]
fn test_empty_vector_int() {
let numbers: Vec<i32> = vec![];
fn test_empty_vector_rel() {
let numbers: Vec<f64> = vec![];
let n = 4;
assert_eq!(sum_with_threads_int_impl(numbers, n), 0);
assert_eq!(sum_with_threads_real_impl(numbers, n), 0.0);
}

#[test]
fn test_large_numbers_int() {
let numbers = vec![1_000_000, 2_000_000, 3_000_000];
fn test_large_numbers_rel() {
let numbers = vec![1_000_000.0, 2_000_000.0, 3_000_000.0];
let n = 3;
assert_eq!(sum_with_threads_int_impl(numbers, n), 6_000_000);
assert_eq!(sum_with_threads_real_impl(numbers, n), 6_000_000.0);
}

#[test]
fn test_negative_numbers_int() {
let numbers = vec![-1, -2, -3, -4, -5];
fn test_negative_numbers_rel() {
let numbers = vec![-1.0, -2.0, -3.0, -4.0, -5.0];
let n = 2;
assert_eq!(sum_with_threads_int_impl(numbers, n), -15);
assert_eq!(sum_with_threads_real_impl(numbers, n), -15.0);
}

#[test]
fn test_mixed_numbers_int() {
let numbers = vec![-1, 2, -3, 4, -5, 6];
fn test_mixed_numbers_rel() {
let numbers = vec![-1.0, 2.0, -3.0, 4.0, -5.0, 6.0];
let n = 3;
assert_eq!(sum_with_threads_int_impl(numbers, n), 3);
assert_eq!(sum_with_threads_real_impl(numbers, n), 3.0);
}

#[test]
fn test_large_vector_int() {
let numbers: Vec<i32> = (1..=1_000).collect();
fn test_large_vector_rel() {
let numbers: Vec<f64> = (1..=1000).map(|x| x as f64).collect();
let n = 4;
let expected_sum: i32 = (1..=1_000).sum();
assert_eq!(sum_with_threads_int_impl(numbers, n), expected_sum);
let expected_sum: f64 = numbers.iter().sum();
assert_eq!(sum_with_threads_real_impl(numbers, n), expected_sum);
}
}

/// Real/doubles
#[test]
fn test_single_thread_rel() {
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let n = 1;
assert_eq!(sum_with_threads_rel_impl(numbers, n), 15.0);
}

#[test]
fn test_multiple_threads_rel() {
let x = vec![1.0, 2.0, 3.0, 4.0];
let num_threads = 2;

let result = sum_with_threads_rel_impl(x, num_threads);
assert_eq!(result, 10.0);
}

#[test]
fn test_more_threads_than_elements_rel() {
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let n = 10;
assert_eq!(sum_with_threads_rel_impl(numbers, n), 15.0);
}

#[test]
fn test_empty_vector_rel() {
let numbers: Vec<f64> = vec![];
let n = 4;
assert_eq!(sum_with_threads_rel_impl(numbers, n), 0);
}

#[test]
fn test_large_numbers_rel() {
let numbers = vec![1_000_000, 2_000_000, 3_000_000];
let n = 3;
assert_eq!(sum_with_threads_rel_impl(numbers, n), 6_000_000);
}

#[test]
fn test_negative_numbers_rel() {
let numbers = vec![-1.0, -2.0, -3.0, -4.0, -5.0];
let n = 2;
assert_eq!(sum_with_threads_rel_impl(numbers, n), -15.0);
}

#[test]
fn test_mixed_numbers_rel() {
let numbers = vec![-1.0, 2.0, -3.0, 4.0, -5.0, 6.0];
let n = 3;
assert_eq!(sum_with_threads_rel_impl(numbers, n), 3.0);
}

#[test]
fn test_large_vector_rel() {
let numbers: Vec<f64> = (1..=1_000).collect();
let n = 4;
let expected_sum: i32 = (1..=1_000).sum();
assert_eq!(sum_with_threads_rel_impl(numbers, n), expected_sum);
}
18 changes: 18 additions & 0 deletions tests/testthat/test-sum_with_threads.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,21 @@ test_that("sums as doubles expected", {
out <- blazr::sum_with_threads(vector_dbl, 5L)
expect_equal(out, sum(vector_dbl))
})

test_that("with non-numeric vectors, errors as expected", {
vector_chr <- c("a", "b", "c")

expect_error(
blazr::sum_with_threads(vector_chr, 5L),
"must be coercible to a numeric vector"
)
})

test_that("with non-integer threads, errors as expected", {
vector_int <- 1:10

expect_error(
blazr::sum_with_threads(1:10, 5.5),
"must be coercible to an integer"
)
})
Loading