Rcpp:提供核动力

R 最早是出于教学的目的,由两位统计学教授 Ross Ihaka 和 Robert Gentleman 在新西兰大学共同发起创建。这就决定了 R 的天然基因是不具备 CS 的考究,即不过多的重视运行性能,而把重点放在快速验证想法。比如,R 属于解释型语言的范畴,不需要事先编译成机器码,只需要把需要处理的单行代码(或者单个代码块)传递给解释器(支持 REPL),就可以得到结果。从开发人员处理统计数据的角度看,这无疑是大大缩短了从想法到验证结果的的痛苦过程;但是,如果是重复执行某个步骤、大量调用某个函数,这就成为了一项瓶颈,毕竟边解释边运行需要消耗大量的运力,无法达到机器码那样的执行速度。

为了提升 R 的处理性能,我们常常绞尽脑汁,比如

  • 使用向量化运算,尽量避免循环操作
  • 使用并行计算,榨干机器的每一个 cpu
  • 把执行的函数放入缓冲,避免重复调用

但是,无可避免的,以上的操作只是杯水车薪,无法从根本上解决 R 作为解释型语言的弱点。为了彻底解决这个问题,一个想法便是为 R 提供底层的代码支持,即绕开解释器、直接调用底层的 c/c++ 执行运算,这样便可以大大的提升 R 的处理能力了。从开发的机制上看,R 本身是由 S 演变过来的,而最早的 S 语言又是使用 c 语言开发的,这意味着 R 实际上也是具备调用底层代码的能力。不过,直接编写允许调用底层 c 接口会比较麻烦,需要处理太多的技术细节。

为此,大牛 Dirk Eddelbuettel 决定跳过 c,使用其高级版本——c++R 提供调用底层的接口能力。这就是我们今天需要隆重介绍的主角:Rcpp

安装与配置

安装软件包

直接使用 CRAN 安装即可

install.packages("Rcpp")

完成后,可以使用命令载入软件包

library(Rcpp)

配置编译环境

通过编辑主目录文件 ~/.R/Makevars 实现对编译环境的配置

## 设置编译环境:~/.R/Makevars

CC=/opt/local/bin/gcc-mp-4.7
CXX=/opt/local/bin/g++-mp-4.7
CPLUS_INCLUDE_PATH=/opt/local/include:$CPLUS_INCLUDE_PATH
LD_LIBRARY_PATH=/opt/local/lib:$LD_LIBRARY_PATH
CXXFLAGS= -g0 -O2 -Wall
MAKE=make -j4 

## for C code
CFLAGS=               -O3 -g0 -Wall -pipe -pedantic -std=gnu99 

## for C++ code
#CXXFLAGS=             -g -O3 -Wall -pipe -Wno-unused -pedantic -std=c++11
CXXFLAGS=             -g -O3 -Wall -pipe -Wno-unused -pedantic 

## for Fortran code
#FFLAGS=-g -O3 -Wall -pipe
FFLAGS=-O3 -g0 -Wall -pipe
## for Fortran 95 code
#FCFLAGS=-g -O3 -Wall -pipe
FCFLAGS=-O3 -g0 -Wall -pipe

# VER=-4.8
# CC=ccache gcc$(VER)
# CXX=ccache g++$(VER)
# SHLIB_CXXLD=g++$(VER)
FC=ccache gfortran
F77=ccache gfortran
MAKE=make -j8

开发

cppFunction

sourceCpp

对于大型的项目,我们一般不会直接使用 cppFunction 来内嵌代码,因为这样对长代码的支持不是特别好,而且无法接入其他的库函数。为此,需要单独在 cpp 的文件进行独立开发,然后使用 sourceCpp 完成编译后,提供给 R 进行调用。

一个常用的 Rcpp.cpp 文件格式为

// =============================================================================
// -------------------------
#include <fstream>
#include <sstream>
#include <string>
#include <Rcpp.h>

using namespace std;
using namespace Rcpp;
// Enable C++11 via this plugin (Rcpp 0.10.3 or later)
// [[Rcpp::plugins(cpp11)]]
// -------------------------

/* -------------------------
 * 通用的格式如下

// [[Rcpp::export]]
RETURN_TYPE FUNCTION_NAME(ARGUMENT_TYPE ARGUMENT){

    //do something

    return RETURN_VALUE;
}

*/
// =============================================================================

比如,我经常使用的一个读取文件的函数

// -----------------------------------------------------------------------------
/*
- rcpp_readFile(datafile):

fast reading datafile
Ref: https://gist.github.com/hadley/6353939
*/
// [[Rcpp::export]]
CharacterVector rcpp_readFile(std::string path) {
    std::ifstream in(path.c_str());
    std::string contents;
    in.seekg(0, std::ios::end);
    contents.resize(in.tellg());
    in.seekg(0, std::ios::beg);
    in.read(&contents[0], contents.size());
    in.close();
    return(contents);
}
// -----------------------------------------------------------------------------

将其保存在 myRcpp.cpp 文件后, 使用命令 sourceCpp 编译生成机器码,即可提供给 R 直接调用了。其中,通过指定 cacheDir = "./tmp/Rcpp" 把编译完成的 .so 文件保存文件夹,如此一来,如果源文件没有改动,则下次调用该函数时,就不需要重新再进行编译,而是直接调用动态链接库,省去了过程当中的编译环节。

sourceCpp( "myRcpp.cpp", verbose = TRUE, cacheDir = "./tmp/Rcpp" )

data <- rcpp_readFile(datafile)
powered by Gitbook该文件最后修改时间: 2019-11-01 15:06:29

results matching ""

    No results matching ""