## ----include = FALSE---------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----include = FALSE---------------------------------------------------------- required <- c("bench", "brio", "callr", "cli", "cpp11", "decor", "desc", "glue", "purrr", "readr", "stringr", "utils", "vctrs", "withr") if (!all(vapply(required, requireNamespace, logical(1), quietly = TRUE))) { knitr::opts_chunk$set(eval = FALSE) knitr::knit_exit() } ## ----include=FALSE------------------------------------------------------------ library(cppally) ## ----include=FALSE------------------------------------------------------------ cpp_source <- function(..., code, debug = FALSE, env = parent.frame()){ preamble <- c("#include ", "using namespace cppally;") code <- paste(c(preamble, code), collapse = "\n") cppally::cpp_source(debug = debug, env = env, code = code, ...) } chunk_impl <- function(x, language){ paste0("```", language, "\n", x, "\n```\n") } as_code_chunk <- function(x, language){ cat(chunk_impl(x, language)) } as_cpp_chunk <- function(x){ as_code_chunk(x, "cpp") } ## ----include=FALSE------------------------------------------------------------ # Compile necessary examples in one-go # All examples are benchmarks so debug = FALSE # cppally-only examples examples <- c( bench_protect_insert_release_cppally = ' #include using namespace cppally; #include [[cppally::register]] double bench_protect_insert_release_cppally(int n) { SEXP dummy = Rf_ScalarInteger(42); R_PreserveObject(dummy); auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < n; ++i) { r_sexp x(dummy); // insert into pool } // destructor → release from pool auto end = std::chrono::high_resolution_clock::now(); R_ReleaseObject(dummy); double ns = std::chrono::duration(end - start).count(); return ns / n; // nanoseconds per insert+release cycle } ', bench_protect_copy_cppally = ' #include using namespace cppally; #include [[cppally::register]] double bench_protect_copy_cppally(int n) { SEXP dummy = Rf_ScalarInteger(42); r_sexp dummy2 = r_sexp(dummy); auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < n; ++i) { r_sexp x = dummy2; // Copy } auto end = std::chrono::high_resolution_clock::now(); double ns = std::chrono::duration(end - start).count(); return ns / n; // nanoseconds per copy } ', cppally_na_count = ' [[cppally::register]] int cppally_na_count(r_vec x){ r_size_t n = x.length(); int na_count = 0; for (r_size_t i = 0; i < n; ++i){ r_str str = x.get(i); // r_str protects the underlying CHARSXP na_count += is_na(str); } return na_count; } ', cppally_fast_na_count = ' [[cppally::register]] int cppally_fast_na_count(r_vec x){ r_size_t n = x.length(); int na_count = 0; for (r_size_t i = 0; i < n; ++i){ r_str_view str = x.get(i); // `r_str_view` does NOT re-protect the underlying CHARSXP na_count += is_na(str); } return na_count; } ', cppally_fast_na_count_v2 = ' [[cppally::register]] int cppally_fast_na_count_v2(r_vec x){ r_size_t n = x.length(); int na_count = 0; for (r_size_t i = 0; i < n; ++i){ // view() is safe in a short-lived read-only context na_count += is_na(x.view(i)); } return na_count; } ' ) # cpp11-using examples (and the R C API baseline). Compiled separately so the # cpp11 includes / linking_to / namespace usage stays out of the cppally-only # translation unit. cpp11_examples <- c( bench_protect_insert_release_cpp11 = ' #include [[cppally::linking_to("cpp11")]] using namespace cpp11; #include [[cppally::register]] double bench_protect_insert_release_cpp11(int n) { SEXP dummy = Rf_ScalarInteger(42); R_PreserveObject(dummy); auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < n; ++i) { sexp x(dummy); // insert into pool } // destructor → release from pool auto end = std::chrono::high_resolution_clock::now(); R_ReleaseObject(dummy); double ns = std::chrono::duration(end - start).count(); return ns / n; // nanoseconds per insert+release cycle } ', bench_protect_copy_cpp11 = ' #include [[cppally::linking_to("cpp11")]] using namespace cpp11; #include [[cppally::register]] double bench_protect_copy_cpp11(int n) { SEXP dummy = Rf_ScalarInteger(42); sexp dummy2 = sexp(dummy); auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < n; ++i) { sexp x = dummy2; // Copy } auto end = std::chrono::high_resolution_clock::now(); double ns = std::chrono::duration(end - start).count(); return ns / n; // nanoseconds per copy } ', C_na_count = ' // Pure R C API NA count - As fast as it can reasonably get [[cppally::register]] // Registered via cppally for convenience int C_na_count(SEXP x){ r_size_t n = Rf_xlength(x); int na_count = 0; const SEXP *p_x = STRING_PTR_RO(x); for (r_size_t i = 0; i < n; ++i){ SEXP str = p_x[i]; // No protection so no extra overhead na_count += str == NA_STRING; } return na_count; } ', cpp11_na_count = ' #include [[cppally::linking_to("cpp11")]] [[cppally::register]] int cpp11_na_count(SEXP x){ using namespace cpp11; strings x_(x); R_xlen_t n = x_.size(); int na_count = 0; for (R_xlen_t i = 0; i < n; ++i){ r_string str = x_[i]; // r_string protects the underlying CHARSXP na_count += cpp11::is_na(str); } return na_count; } ' ) # Display-only snippets (no [[cppally::register]] tag, so not exposed to R). # Compiled in their own batch since they are illustrative rather than benchmarked. display_only <- c( view_good = ' void good(r_str x){ r_str_view str = x; if (str.cpp_str() == "true"){ print("true"); } else { print("false"); } } ', view_bad = ' r_str_view bad(){ r_str new_str("I will be destroyed at the end of `bad()`"); r_str_view bad_str = new_str; // A view of new_str return bad_str; // Points to underlying CHARSXP but nothing protecting it } ' ) cpp_source(code = paste(examples, collapse = "\n")) cpp_source(code = paste(cpp11_examples, collapse = "\n")) cpp_source(code = paste(display_only, collapse = "\n")) ## ----------------------------------------------------------------------------- library(cppally) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(cpp11_examples[["bench_protect_insert_release_cpp11"]]) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(examples[["bench_protect_insert_release_cppally"]]) ## ----------------------------------------------------------------------------- insert_release_cpp11 <- replicate(10^4, bench_protect_insert_release_cpp11(10^4)) mean(insert_release_cpp11) insert_release_cppally <- replicate(10^4, bench_protect_insert_release_cppally(10^4)) mean(insert_release_cppally) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(cpp11_examples[["bench_protect_copy_cpp11"]]) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(examples[["bench_protect_copy_cppally"]]) ## ----------------------------------------------------------------------------- copy_sexp_cpp11 <- replicate(10^4, bench_protect_copy_cpp11(10^4)) mean(copy_sexp_cpp11) copy_sexp_cppally <- replicate(10^4, bench_protect_copy_cppally(10^4)) mean(copy_sexp_cppally) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(cpp11_examples[["C_na_count"]]) as_cpp_chunk(cpp11_examples[["cpp11_na_count"]]) as_cpp_chunk(examples[["cppally_na_count"]]) ## ----------------------------------------------------------------------------- set.seed(42) x <- sample(letters, 10^5, TRUE) x[sample.int(length(x), 10^3)] <- NA ## ----------------------------------------------------------------------------- library(bench) mark(C_na_count(x)) ## ----------------------------------------------------------------------------- mark(cpp11_na_count(x)) ## ----------------------------------------------------------------------------- mark(cppally_na_count(x)) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(examples[["cppally_fast_na_count"]]) ## ----------------------------------------------------------------------------- mark(cppally_fast_na_count(x)) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(examples[["cppally_fast_na_count_v2"]]) ## ----------------------------------------------------------------------------- mark(cppally_fast_na_count_v2(x)) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(display_only[["view_good"]]) ## ----echo=FALSE, comment="", results='asis'----------------------------------- as_cpp_chunk(display_only[["view_bad"]])