cpp11-style helpers to compile cppally code outside of a cppally-linked package context.
cpp_source() compiles and loads a single C++ file for use in R,
either from an expression or a cpp file.
This may include multiple C++ functions.
cpp_eval() evaluates a single C++ expression and returns the result.
For example cpp_eval('get_threads()') will run the C++ function
cppally::get_threads() and return the number of OMP threads currently set
for use. For expressions no return result, the call is evaluated and returns
NULL invisibly.
Usage
cpp_source(
file,
code = NULL,
env = parent.frame(),
clean = TRUE,
quiet = TRUE,
debug = FALSE,
preserve_altrep = FALSE,
check_factors = FALSE,
check_data_frames = FALSE,
copy_on_modify = FALSE,
cxx_std = Sys.getenv("CXX_STD", "CXX20"),
dir = tempfile()
)
cpp_eval(
code,
env = curr_env(),
clean = TRUE,
quiet = TRUE,
debug = FALSE,
preserve_altrep = FALSE,
check_factors = FALSE,
check_data_frames = FALSE,
copy_on_modify = FALSE,
simplify = TRUE,
cxx_std = Sys.getenv("CXX_STD", "CXX20")
)Arguments
- file
C++ file.
- code
For
cpp_source()- IffileisNULLthen a string of C++ code to compile. This can include the contents of a cpp file which can contain multiple[[cppally::register]]tagged functions. Forcpp_eval- This can be a character vector of single-line expressions.- env
Environment where R functions should be defined.
- clean
Should files be cleaned up after sourcing? Default is
TRUE.- quiet
Should compiler output be suppressed? Default is
TRUE.- debug
Should C++ code be compiled in a debug build? Default is
FALSE.- preserve_altrep
Should ALTREP vectors be preserved by avoiding materialisation where possible? Default is
FALSE.- check_factors
Should factor levels be validated when using
r_factorsobjects? Default isFALSE. WhenTRUE, factor levels are checked once onr_factorsconstruction to ensure they are valid, reducing the chance of R crashing when passing factors with invalid levels.- check_data_frames
Should data frames be validated when constructing
r_dfobjects fromSEXP? Default isFALSE.- copy_on_modify
Should copy-on-modify be used everywhere? Default is
FALSE.- cxx_std
C++ standard to use. Should be >= C++20.
- dir
Directory to store the source files. The default is a temporary directory via
tempfile()which is removed whenclean = TRUE.- simplify
Applies to
cpp_eval. A list of results is returned unlesslength(code) == 1andsimplify = TRUE.
Value
cpp_source() invisibly compiles the C++ code and registers
the [[cppally::register]] tagged functions to R. cpp_eval() returns the results of the evaluated C++ expressions.
Examples
library(cppally)
library(bit64)
#>
#> Attaching package: ‘bit64’
#> The following object is masked from ‘package:utils’:
#>
#> hashtab
#> The following objects are masked from ‘package:base’:
#>
#> %in%, :, array, as.factor, as.ordered, colSums, factor, intersect,
#> is.double, is.element, match, matrix, order, rank, rowSums,
#> setdiff, setequal, table, union
# \donttest{
cpp_eval('print("hello world!")')
#> hello world!
# Default values of all cppally scalars
cpp_eval(c(
'r_lgl()',
'r_int()',
'r_dbl()',
'r_int64()',
'r_str()',
'r_raw()',
'r_cplx()',
'r_date()',
'r_psxct()'
))
#> $res1
#> [1] FALSE
#>
#> $res2
#> [1] 0
#>
#> $res3
#> [1] 0
#>
#> $res4
#> integer64
#> [1] 0
#>
#> $res5
#> [1] ""
#>
#> $res6
#> [1] 00
#>
#> $res7
#> [1] 0+0i
#>
#> $res8
#> [1] "1970-01-01"
#>
#> $res9
#> [1] "1970-01-01 UTC"
#>
cpp_source(code = '
#include <cppally.hpp>
using namespace cppally;
[[cppally::register]]
r_dbl add(r_dbl x, r_dbl y){
return x + y;
}
', debug = TRUE)
add(1, 2)
#> [1] 3
add(2, NA)
#> [1] NA
### ALTREP ###
# cppally also supports lazy ALTREP materialisation as an opt-in feature.
# To opt-in, set `preserve_altrep = TRUE`
cpp_source(
code = '
#include <cppally.hpp>
using namespace cppally;
[[cppally::register]]
r_int last_altrep_unaware(r_vec<r_int> x){
r_int out;
r_size_t n = x.length();
if (n > 0){
out = x.get(n - 1);
}
return out;
}
', debug = TRUE
)
cpp_source(
code = '
#include <cppally.hpp>
using namespace cppally;
[[cppally::register]]
r_int last_altrep_aware(r_vec<r_int> x){
r_int out;
r_size_t n = x.length();
if (n > 0){
out = x.get(n - 1);
}
return out;
}
', debug = TRUE,
preserve_altrep = TRUE
)
library(bench)
mark(last_altrep_aware(1:10^5)) # No materialisation
#> # A tibble: 1 × 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
#> 1 last_altrep… 4.18µs 5.08µs 181012. 3.18KB 0 10000 0 55.2ms
#> # ℹ 4 more variables: result <list>, memory <list>, time <list>, gc <list>
mark(last_altrep_unaware(1:10^5)) # Materialises full vector
#> # A tibble: 1 × 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
#> 1 last_altrep… 38.6µs 44.1µs 17114. 391KB 139. 3329 27 195ms
#> # ℹ 4 more variables: result <list>, memory <list>, time <list>, gc <list>
### Copy-on-modify ###
# cppally supports copy-on-modify as an opt-in feature
# It is disabled by default because it incurs a major performance penalty
# and has been deemed not worth it even for the safety benefits
# That being said, if you prefer absolute safety over speed then you can
# enable it globally via `cppally::use_copy_on_modify()` or
# via the arg `copy_on_modify` if using `cpp_source()`
cpp_source(
code = '
#include <cppally.hpp>
using namespace cppally;
[[cppally::register]]
r_vec<r_int> reverse(r_vec<r_int> x){
x.rev(); // in-place reverse
return x;
}
', copy_on_modify = TRUE
)
x <- c(1L, 2L, 3L)
reverse(x)
#> [1] 3 2 1
x # x was preserved and not updated by reference (as expected)
#> [1] 1 2 3
x <- sample.int(10^5)
mark(reverse(x)) # Memory allocated, therefore x was copied before reversing
#> # A tibble: 1 × 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
#> <bch:expr> <bch:tm> <bch:> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
#> 1 reverse(x) 447µs 478µs 1905. 391KB 13.5 707 5 371ms
#> # ℹ 4 more variables: result <list>, memory <list>, time <list>, gc <list>
# The cppally preferred approach is to allocate a fresh vector or copy the
# existing vector
cpp_source(
code = '
#include <cppally.hpp>
using namespace cppally;
[[cppally::register]]
r_vec<r_int> cppally_reverse(r_vec<r_int> x){
r_vec<r_int> out = shallow_copy(x);
out.rev();
return out;
}
', copy_on_modify = FALSE
)
mark(
r_reverse = rev(x),
cppally_copy_on_modify_reverse = reverse(x),
cppally_no_copy_on_modify_reverse = cppally_reverse(x)
)
#> # A tibble: 3 × 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
#> <bch:expr> <bch:t> <bch:t> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
#> 1 r_reverse 238.5µs 244µs 3676. 781KB 61.8 1189 20 323ms
#> 2 cppally_c… 447.9µs 461.9µs 2006. 391KB 15.0 938 7 468ms
#> 3 cppally_n… 66.4µs 70.5µs 9421. 391KB 73.6 3586 28 381ms
#> # ℹ 4 more variables: result <list>, memory <list>, time <list>, gc <list>
# }