rmin - A minimal Rust lib for writting R extensions
This is a very early version, only support vector type, and thus its overhead is minimized.
Compare to the well-knowned rextendr, This crate although with some limitations but could provide a faster implementation, a smaller code size, and a faster compile time ( could generate a release build in 0.45s ).
Since it is small, you could vendor this crate easily into your CRAN package.
Usage
Version 0.1.0 provides a fastest (but ugly) way to achieve about 2x speedup on with functions. They are discarded in 0.2.* since they are really unsafe and may cause memory leak.
I cannot ensure whether the api will change again in the future, but currently v0.2.0 seems usable.
0.2.0, rewrite to prevent memory leak
Several changes have been made since v0.1.0:
- Add a panic_handler with default
stdfeature, it is because currently, Rust cannot call all drop function before a longjmp is executed, which will cause memory leak, thuscatch_unwindmust be used and thusstdcrate is needed. I have not check whether#![no_std]works now, but it is better not to use it. - Move
SEXPtolibR::SEXP, and exportingSEXP<T>,Owned<T>(andProtected<T>generated byOwned<T>::protectwhich cannot be used directly, since returnProtected<T>to R will break the protect balance) - Currently, you could directly indexing a sexp object, but convert them firstly into slices is more preferred.
grammar
use *;
/// Return a+b to R.
pub extern "C-unwind"
pub extern "C-unwind"
/// raise panic.
pub extern "C-unwind"
The program above could be tested with test command
; LC_ALL=C ; LC_ALL=C
0.1.0, the beginning
The grammar provided by 0.1.0 might be changed in the future, since they lack compile time checks (for exapmle, writting a SEXP<f64> is better than using plain SEXP directly.)
In later version, I may adding proc-macro support thus we could write better program.
Currently, we have to write program by hand.
grammar
// this is required. At least it is required in v0.1.0
use *;
/// without `#[no_mangle]` and `extern`, you might not seen this function in R.
/// the signature should be a lot of SEXP to a Owned SEXP.
/// please do not consider send a `Protected` back to R, since it would cause memory leak.
/// Sometimes you may send SEXP directly, that's OK.
pub extern
/// No need to register the function again (as what we should do in rextendr)
/// instead, we should register them with R code, at least now it should.
The program above could be tested with test command
LC_ALL=C
benchmark
0.1.0 yields
> ;;
user system elapsed
0.161 0.007 0.168
user system elapsed
0.148 0.000 0.149
>
>
To further speedup calculation, we could use
LC_ALL=C
which yields
>
> ;
user system elapsed
0.178 0.000 0.178
>
user system elapsed
0.149 0.003 0.152
>
user system elapsed
0.109 0.000 0.108
>
user system elapsed
0.11 0.00 0.11
>
>
Compare with rextendr:
LC_ALL=C
Ignored some unhappy error (I have no idea why path='/me/fine' compiles, path=/me/notfine yield an error about Failed to generate wrapper functions.), we could got:
> path='/me/fine'
> setwd(path)
> usethis::create_package('.')
v Setting active project to '/me/fine'
v Creating 'R/'
v Writing 'DESCRIPTION'
Package: fine
Title: What the Package Does (One Line, Title Case)
Version: 0.0.0.9000
Authors@R (parsed):
* First Last <first.last@example.com> [aut, cre] (YOUR-ORCID-ID)
Description: What the package does (one paragraph).
License: `use_mit_license()`, `use_gpl3_license()` or friends to
pick a license
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1
v Writing 'NAMESPACE'
v Setting active project to '<no active project>'
> rextendr::use_extendr()
i First time using rextendr. Upgrading automatically...
i Setting `Config/rextendr/version` to "0.3.1"
v Creating src/rust/src.
v Setting active project to '/me/fine'
v Writing 'src/entrypoint.c'
v Writing 'src/Makevars'
v Writing 'src/Makevars.win'
v Writing 'src/Makevars.ucrt'
v Writing 'src/.gitignore'
v Writing src/rust/Cargo.toml
v Writing 'src/rust/src/lib.rs'
v Writing 'src/fine-win.def'
v Writing 'R/extendr-wrappers.R'
v Finished configuring extendr for package fine.
* Please update the system requirement in DESCRIPTION file.
* Please run `rextendr::document()` for changes to take effect.
> cat('use extendr_api::prelude::*;\n#[extendr]\nfn test(a:i32,b:i32)->i32 { a + b }\nextendr_module! {\n mod fine;\n fn test;\n}\n', file=paste(path,'src/rust/src/lib.rs',sep='/'))
> rextendr::document()
i Generating extendr wrapper functions for package: fine.
i Re-compiling fine (debug build)
-- R CMD INSTALL -------------------------------------------------------------------------------------------------------------------------------------------------------------------
- installing *source* package 'fine' ...
** using staged installation
** libs
using C compiler: 'gcc (GCC) 14.1.1 20240522'
rm -Rf fine.so ./rust/target/release/libfine.a entrypoint.o
gcc -I"/usr/include/R/" -DNDEBUG -I/usr/local/include -fpic -O2 -march=native -pipe -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -g -ffile-prefix-map=/build/r/src=/usr/src/debug/r -flto=auto -ffat-lto-objects -UNDEBUG -Wall -pedantic -g -O0 -fdiagnostics-color=always -c entrypoint.c -o entrypoint.o
# In some environments, ~/.cargo/bin might not be included in PATH, so we need
# to set it here to ensure cargo can be invoked. It is appended to PATH and
# therefore is only used if cargo is absent from the user's PATH.
if [ "true" != "true" ]; then \
export CARGO_HOME=/me/fine/src/.cargo; \
fi && \
export PATH="/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/cuda/bin:/opt/cuda/nsight_compute:/opt/cuda/nsight_systems/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/home/neutron/.cargo/bin" && \
cargo build --lib --release --manifest-path=./rust/Cargo.toml --target-dir ./rust/target
Updating `ustc` index
Locking 10 packages to latest compatible versions
Compiling proc-macro2 v1.0.84
Compiling unicode-ident v1.0.12
Compiling libR-sys v0.6.0
Compiling paste v1.0.15
Compiling extendr-api v0.6.0
Compiling once_cell v1.19.0
Compiling quote v1.0.36
Compiling syn v2.0.66
Compiling extendr-macros v0.6.0
Compiling fine v0.1.0 (/me/fine/src/rust)
Finished `release` profile [optimized] target(s) in 34.90s
if [ "true" != "true" ]; then \
rm -Rf /me/fine/src/.cargo && \
rm -Rf ./rust/target/release/build; \
fi
gcc -shared -L/usr/lib64/R/lib -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now -flto=auto -o fine.so entrypoint.o -L./rust/target/release -lfine -L/usr/lib64/R/lib -lR
installing to /tmp/RtmpMMeEfN/devtools_install_c08929da6615/00LOCK-fine/00new/fine/libs
** checking absolute paths in shared objects and dynamic libraries
- DONE (fine)
v Writing 'R/extendr-wrappers.R'
i Updating fine documentation
Writing NAMESPACE
i Loading fine
x extendr-wrappers.R:12: `@docType "package"` is deprecated.
i Please document "_PACKAGE" instead.
Writing fine-package.Rd
> system.time(sapply(1:100000,function(x)test(1,2)))
user system elapsed
0.188 0.017 0.205
> system.time(sapply(1:100000,function(x)test(1,2)))
user system elapsed
0.174 0.003 0.178
> system.time(sapply(1:100000,function(x).Call(wrap__test,1,2)))
user system elapsed
0.147 0.007 0.155
> system.time(sapply(1:100000,function(x).Call(wrap__test,1,2)))
user system elapsed
0.142 0.000 0.142
>
>
besides a longer compiling time, the final program is slower than this crate. This is the main reason why I wrote this crate.
misc
Feel free to file an issue in case you meet some problem :)
I'm a Linux user, but I'll try my best to solve some windows only problem.