Shixiang Wang

>上士闻道
勤而行之

Rcpp 简介:从 R 到 Rcpp

王诗翔 · 2020-09-08

分类: r  
标签: r   rcpp   c++  

参考图书:《Rcpp:R 与 C++ 的无缝整合》

Rcpp 的主要目的在于使得开发 R 语言的 C++ 相关拓展变得更加容易、更少出错。

我们首先从斐波那契数列问题开始探索 Rcpp。

该问题是一个递归问题,首两项为 0 和 1,而后面每一项为前两项之和。

C++ 实现,直接根据定义进行实现:

int fibonacci(const int x) {
  if (x == 0) return(0);
  if (x == 1) return(1);
  return fibonacci(x - 1) + fibonacci(x - 2); 
}

R 通过 .Call() 可以在 R 和 C++ 之间传递对象。该函数使用的变量必须是指向 S 表达式的指针(简称 SEXP),这需要新建一个封装器:

#include <Rcpp.h>
extern "C" SEXP fibWrapper(SEXP xs) {
 int x = Rcpp::as<int>(xs);
 int fib = fibonacci(x);
 return (Rcpp:wrap(fib));
}

aswrap 是 Rcpp 很重要的两个转换函数,这里 as 将输入参数 xs 由 R 输入的 SEXP 类型转换为整型,而 wrap 将 c++ 得到的整型结果封装为 SEXP 类型,从而可以使得这个创建的函数可以被 .Call() 调用,完成 c++ 的计算与输入输出的相互传递。

在写好上述两个函数后,后面的工作就是编译函数,生成所谓的“共享库”,这样 R 就可以加载和调用它。编译、链接和加载是一个纯粹的体力活,幸好,工具包 inline 可以帮助我们完成这 3 个步骤。

inline 包通过提供一个涵盖编译、链接、加载三个步骤的完整封装器,因而程序员可以集中经精力在真正工作的代码上(C、C++ 和 Fortran 三者之一),而忽略针对不同操作系统特定的编译、链接、加载细节。

cxxfunction() 是一个单一入口,可以将文本变量传入的代码转换为可运行的函数!

cxxcode <- "
int fibonacci(const int x) {
  if (x == 0) return(0);
  if (x == 1) return(1);
  return fibonacci(x - 1) + fibonacci(x - 2); 
}
"

library(inline)
fibRcpp <- cxxfunction(signature(xs = "int"),
  plugin = "Rcpp",
  includes = cxxcode,
  body = "
    int x = Rcpp::as<int>(xs);
    return Rcpp::wrap(fibonacci(x));
                       "
)

这样 fibRcpp() 就可以在 R 中调用了。

sapply(1:10, fibRcpp)
#>  [1]  1  1  2  3  5  8 13 21 34 55

在后续版本的 inline 包中,它通过引入 C++ 的 “attributes” 特性进一步对上述过程进行简化,自动完成变量的类型转换等工作。

fibonacci.cpp 中写入如下内容:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
int fibonacci(const int x) {
  if (x < 2) return(x);
  return fibonacci(x - 1) + fibonacci(x - 2); 
}

这里在函数的定义前通过注释行添加了 [[Rcpp::export]] 这个属性,然后该代码即可通过 sourceCpp() 进行调用。

sourceCpp("fibonacci.cpp)
fibonacci(20)

该函数进行如下的处理:从给定的源代码文件中读取代码,解析相应的属性,并调用 R 在编译前生成所需要的封装器,之后像之前一样进行编译、链接和加载。

可以看到,通过 R 调用 c++ 简化到了仅仅添加一些必要的头文件和属性信息即可,不需要改动工作代码本身。当然,强大的 Rcpp 不仅仅如此,它还提供了诸多的与 R 交互的数据类型,后续再学习分享。