{cpp11}

Nova Interface C++11 STL do pessoal do RStudio

Jose Storopoli https://scholar.google.com/citations?user=xGU7H1QAAAAJ&hl=en (UNINOVE)https://www.uninove.br
February 2, 2021

O {cpp11} é uma nova interface entre R e C++ desenvolvida pela equipe do RStudio.

Ainda é muito nova com apenas 13 links reversos de pacotes do CRAN, e com um total de 3.759.798 downloads (de 01/01/2016 à 31/12/2020).

Principais Motivações do {cpp11}

As mudanças que motivaram {cpp11} incluem:

Como usar {cpp11} no seu código C++

A sistemática é bem similar ao {Rcpp} e bem simples também. Primeiro, instale o pacote {cpp11} no R.

Segundo, includa no começo de todos seus arquivos .cpp e códigos C++:

#include <cpp11.hpp>
using namespace cpp11;
namespace writable = cpp11::writable;

Terceiro, para cada função que desejar ser exportada de volta ao ambiente de R, inclua o seguinte prefixo antes da definição da função:

[[cpp11::register]] ReturnType functionName(){return x;}

Tipos de Dados – {Rcpp} vs {cpp11}

O {cpp11} possui os mesmos tipos de dados que o {Rcpp}, mas ele permite um melhor controle pois conseguimos especificar o que é somente leitura e o que é “gravável” (writable). Além disso os headers são bem organizados e você não precisa trazer uma tralha toda que nem no {Rcpp}. Caso queira usar apenas strings use o header <cpp11/strings.hpp>. Abaixo uma tabela de referência de {Rcpp} vs {cpp11}.

Rcpp cpp11 (somente leitura) cpp11 ("gravável") cpp11 header

NumericVector

doubles

writable::doubles

<cpp11/doubles.hpp>

IntegerVector

integers

writable::integers

<cpp11/integers.hpp>

CharacterVector

strings

writable::strings

<cpp11/strings.hpp>

RawVector

raws

writable::raws

<cpp11/raws.hpp>

List

list

writable::list

<cpp11/list.hpp>

RObject

sexp

-

<cpp11/sexp.hpp>

XPtr

-

external_pointer

<cpp11/external_pointer.hpp>

Environment

-

environment

<cpp11/environment.hpp>

Function

-

function

<cpp11/function.hpp>

Environment (namespace)

-

package

<cpp11/function.hpp>

wrap

-

as_sexp

<cpp11/as.hpp>

as

-

as_cpp

<cpp11/as.hpp>

stop

stop

-

<cpp11/protect.hpp>

Além disso, na tabela abaixo é possível ver a comparação entre as escalares de R vs {cpp11}:

R cpp11

numeric

double

integer

int

character

r_string

logical

bool

Como exportar funções {cpp11} para o ambiente R

{cpp11} funciona quase que de maneira idêntica que o {Rcpp}, apenas a nomenclatura muda. {Rcpp} usa camelCase e {cpp11} usa snake_case. Veja abaixo ma tabela comparativa:

Rcpp cpp11

cppFunction()

cpp_function()

sourceCpp()

cpp_source()

Exemplo – Soma dos Quadrados

Vamos reutilizar o exemplo sum_of_squares do tutorial 2. Como incorporar C++ no R - {Rcpp}.

Soma dos quadrados é algo que ocorre bastante em computação científica, especialmente quando estamos falando de regressão, mínimos quadrados, ANOVA etc. Vamos comparar a função usando um std::acummulate de C+11 STL2 tanto no {Rcpp} quanto no {cpp11}. Lembrando que esta implementação será uma função que aceita como parâmetro um vetor de números reais (C++ double / R numeric) e computa a soma de todos os elementos do vetor elevados ao quadrado.

Para ser uma comparação justa, vamos usar também do tutorial 2. Como incorporar C++ no R - {Rcpp}, a função sum_of_squares_rcpp_sugar() que usa {Rcpp} Sugar.

#include <Rcpp.h>
#include <numeric>

using namespace Rcpp;

// [[Rcpp::plugins("cpp11")]]

// [[Rcpp::export]]
double sum_of_squares_rcpp(NumericVector v){
  double sum_of_elems = 0;

  sum_of_elems += std::accumulate(v.cbegin(),
                                  v.cend(),
                                  0.0,
                                  [] (double i, double j) {return i + (j * j);});
  return sum_of_elems;
}

// [[Rcpp::export]]
double sum_of_squares_rcpp_sugar(NumericVector v){
  return(sum(v*v));
}
#include "cpp11/doubles.hpp"  // aqui usando somente o header doubles
#include <numeric>

using namespace cpp11;
namespace writable = cpp11::writable;


[[cpp11::register]] double sum_of_squares_cpp11(doubles v){
  double sum_of_elems = 0;

  sum_of_elems += std::accumulate(v.cbegin(),
                                  v.cend(),
                                  0.0,
                                  [] (double i, double j) {return i + (j * j);});
  return sum_of_elems;
}
set.seed(123)
b1 <- bench::press(
  n = 10^c(4:6),
  {
    v = rnorm(n)
    bench::mark(
      Rcpp = sum_of_squares_rcpp(v),
      cpp11 = sum_of_squares_cpp11(v),
      Rcppsugar = sum_of_squares_rcpp_sugar(v),
      check = FALSE,
      relative = TRUE
)
  })
b1
# A tibble: 9 x 7
  expression       n   min median `itr/sec` mem_alloc `gc/sec`
  <bch:expr>   <dbl> <dbl>  <dbl>     <dbl>     <dbl>    <dbl>
1 Rcpp         10000  1.02   1.02      1.12       Inf      NaN
2 cpp11        10000  1.24   1.19      1          NaN      NaN
3 Rcppsugar    10000  1      1         1.18       Inf      Inf
4 Rcpp        100000  1.00   1         1.04       Inf      NaN
5 cpp11       100000  1.03   1.03      1          NaN      NaN
6 Rcppsugar   100000  1      1.00      1.03       Inf      NaN
7 Rcpp       1000000  1      1.00      1.02       Inf      NaN
8 cpp11      1000000  1.01   1.02      1          NaN      NaN
9 Rcppsugar  1000000  1.00   1         1.02       Inf      Inf
Benchmarks de Soma dos Quadrados: `Rcpp` vs `cpp11`

Figure 1: Benchmarks de Soma dos Quadrados: Rcpp vs cpp11

Quando usada a biblioteca padrão C++11 STL tanto {cpp11} quanto {Rcpp} e {Rcpp} Sugar, pelo menos neste simples benchmark e no meu computador, possuem um desempenho similar para um vetor com function () , {, length(peek_mask(“n()”)$current_rows()), } elementos. É claro que solução do {Rcpp} Sugar é muito mais elegante e simples com uma única linha na função.

{cpp11} e Rmarkdown

Na mesma lógica de {Rcpp}, para usar o {cpp11} em documentos rmarkdown basta colocar cpp11 no chunk ao invés de r. Além disso é necessário instalar o pacote {decor}. Não esqueça de chamar um library(cpp11) no arquivo rmarkdown.

`{cpp11}` no Rmarkdown

Figure 2: {cpp11} no Rmarkdown

Usar {cpp11} no seu pacote R

Para adicionar {cpp11} a um pacote existente, coloque seus arquivos C++ no diretório src/ e adicione no arquivo DESCRIPTION:

LinkingTo: cpp11

A maneira mais fácil de configurar isso automaticamente é chamar usethis::use_cpp11() do pacote {usethis}.

Antes de construir o pacote, você precisará executar cpp11::cpp_register(). Esta função verifica os arquivos C++ em busca de atributos [[cpp11::register]] e gera o código de ligação necessário para disponibilizar as funções em R. Execute novamente cpp11::cpp_register() sempre que as funções forem adicionadas, removidas ou seus nomes forem alterados. Se você estiver usando {devtools} para desenvolver seu pacote, isso é feito automaticamente pelo pacote {pkgbuild} quando seu pacote tem LinkingTo: cpp11 em seu arquivo DESCRIPTION.

Ambiente

R version 4.0.4 (2021-02-15)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.10

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
[1] gt_0.2.2    cpp11_0.2.6 dplyr_1.0.5

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.6        tidyr_1.1.3       png_0.1-7        
 [4] ps_1.6.0          assertthat_0.2.1  rprojroot_2.0.2  
 [7] digest_0.6.27     utf8_1.1.4        R6_2.5.0         
[10] backports_1.2.1   evaluate_0.14     highr_0.8        
[13] httr_1.4.2        ggplot2_3.3.3     pillar_1.5.1     
[16] rlang_0.4.10      curl_4.3          rstudioapi_0.13  
[19] callr_3.5.1       jquerylib_0.1.3   checkmate_2.0.0  
[22] rmarkdown_2.7     textshaping_0.3.2 desc_1.3.0       
[25] stringr_1.4.0     igraph_1.2.6      munsell_0.5.0    
[28] compiler_4.0.4    xfun_0.22         pkgconfig_2.0.3  
[31] systemfonts_1.0.1 htmltools_0.5.1.1 downlit_0.2.1    
[34] tidyselect_1.1.0  tibble_3.1.0      fansi_0.4.2      
[37] crayon_1.4.1      brio_1.1.1        commonmark_1.7   
[40] crandep_0.1.1     grid_4.0.4        jsonlite_1.7.2   
[43] gtable_0.3.0      lifecycle_1.0.0   DBI_1.1.1        
[46] magrittr_2.0.1    scales_1.1.1      bench_1.1.1      
[49] profmem_0.6.0     cli_2.3.1         stringi_1.5.3    
[52] debugme_1.1.0     farver_2.1.0      xml2_1.3.2       
[55] decor_1.0.0       bslib_0.2.4       ellipsis_0.3.1   
[58] ragg_1.1.1        generics_0.1.0    vctrs_0.3.6      
[61] distill_1.2       tools_4.0.4       cranlogs_2.1.1   
[64] glue_1.4.2        purrr_0.3.4       processx_3.4.5   
[67] parallel_4.0.4    yaml_2.2.1        colorspace_2.0-0 
[70] rvest_1.0.0       knitr_1.31        sass_0.3.1       

  1. o {Rcpp} também tem suporte nativo a strings UTF-8.↩︎

  2. {cpp11} ainda não possui suporte à funcionalidades de C++20 – std::transform_reduce().↩︎

Corrections

If you see mistakes or want to suggest changes, please create an issue on the source repository.

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY-SA 4.0. Source code is available at https://github.com/storopoli/Rcpp, unless otherwise noted. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".

Citation

For attribution, please cite this work as

Storopoli (2021, Feb. 2). Rcpp - A interface entre R e C++: `{cpp11}`. Retrieved from https://storopoli.github.io/Rcpp/5-cpp11.html

BibTeX citation

@misc{storopoli2021cpp11,
  author = {Storopoli, Jose},
  title = {Rcpp - A interface entre R e C++: `{cpp11}`},
  url = {https://storopoli.github.io/Rcpp/5-cpp11.html},
  year = {2021}
}