【问题标题】:Using C++ libraries in an R package在 R 包中使用 C++ 库
【发布时间】:2014-12-29 04:11:15
【问题描述】:

在 R 中使用 C++ 库的最佳方式是什么,希望能保留 C++ 数据结构。我根本不是 C++ 用户,所以我不清楚可用方法的相对优点。 R-ext 手册似乎建议将每个 C++ 函数包装在 C 中。但是,至少存在四到五种其他方法来合并 C++。

两种方式是具有相似血统的包,Rcpp(由多产的溢出者 Dirk Eddelbuettel 维护)和 RcppTemplate 包(都在 CRAN 上),两者之间有什么区别?

R forge 上的另一个包 rcppbind 可用,它声称采用不同的方法来绑定 C++ 和 R(我不知道如何判断)。

CRAN 上可用的包内联,声称允许内联 C/C++ 我不确定这与内置功能有什么不同,除了允许代码内联 w/R。

最后是 RSwig,它似乎是 in the wild,但不清楚它的支持程度,因为 author's page 已经多年没有更新了。

我的问题是,这些不同方法的相对优点是什么。哪些是最便携和最健壮的,哪些是最容易实现的。如果您打算在 CRAN 上分发包,您会使用哪种方法?

【问题讨论】:

    标签: c++ r rcpp


    【解决方案1】:

    首先,免责声明:我一直使用Rcpp。事实上,当(被 Rcpp 重命名的时候)RcppTemplate 已经成为孤儿并且两年没有更新时,我开始以它的初始名称 Rcpp 维护它(它已被贡献给RQuantLib)。那是大约一年前,我做了一些增量更改,您可以在 ChangeLog 中找到这些更改。

    现在,RcppTemplate 在整整 35 个月后又回来了,没有任何更新或修复。它包含有趣的新代码,但它似乎不向后兼容,所以我不会在已经使用过 Rcpp 的地方使用它。

    Rcppbind 在我检查时并没有得到很好的维护。 Whit Armstrong 还有一个名为rabstraction 的模板接口包。

    Inline 完全不同:它通过将程序“嵌入”为 R 字符串,然后编译、链接和加载,从而简化了编译/链接周期。我已经与 Oleg 讨论了内联支持 Rcpp 的问题。

    Swig 也很有趣。 Joe Wang 在那里做得很好,并为 R 包装了所有 QuantLib。但是当我上次尝试它时,由于 R 内部结构的一些变化,它不再起作用。据 Swig 团队的人说,Joe 可能仍在努力。无论如何,Swig 的目标是更大的库。该项目可能会复兴,但并非没有技术挑战。

    另一个值得一提的是RInside,它与 Rcpp 一起使用,让您可以将 R 嵌入到 C++ 应用程序中。

    总结一下:Rcpp 对我来说效果很好,特别是对于您只想添加一两个功能的小型探索性项目。它的重点是易用性,它允许您“隐藏”一些使用起来并不总是很有趣的 R 内部结构。我知道我通过电子邮件断断续续地帮助过许多其他用户。所以我会说去这个。

    我的“使用 R 的 HPC 简介”教程包含一些 Rcpp、RInside 和内联示例。

    编辑:让我们看一个具体的例子(取自“HPC with R Intro”幻灯片,并借自 Stephen Milborrow,后者取自 Venables 和 Ripley)。任务是枚举每个位置仅包含单个数字的 2x2 矩阵的行列式的所有可能组合。这可以通过巧妙的矢量化方式(正如我们在教程幻灯片中讨论的那样)或通过如下蛮力来完成:

    #include <Rcpp.h>
    
    RcppExport SEXP dd_rcpp(SEXP v) {
      SEXP  rl = R_NilValue;        // Use this when there is nothing to be returned.
      char* exceptionMesg = NULL;   // msg var in case of error
    
      try {
        RcppVector<int> vec(v);     // vec parameter viewed as vector of ints
        int n = vec.size(), i = 0;
        if (n != 10000) 
           throw std::length_error("Wrong vector size");
        for (int a = 0; a < 9; a++)
          for (int b = 0; b < 9; b++)
            for (int c = 0; c < 9; c++)
              for (int d = 0; d < 9; d++)
                vec(i++) = a*b - c*d;
    
        RcppResultSet rs;           // Build result set to be returned as list to R
        rs.add("vec", vec);         // vec as named element with name 'vec'
        rl = rs.getReturnList();    // Get the list to be returned to R.
      } catch(std::exception& ex) {
        exceptionMesg = copyMessageToR(ex.what());
      } catch(...) {
        exceptionMesg = copyMessageToR("unknown reason");
      }
    
      if (exceptionMesg != NULL) 
         Rf_error(exceptionMesg);
    
      return rl;
    }
    

    如果您将其保存为 dd.rcpp.cpp 并安装了 Rcpp,则只需使用

    PKG_CPPFLAGS=`Rscript -e 'Rcpp:::CxxFlags()'`  \
        PKG_LIBS=`Rscript -e 'Rcpp:::LdFlags()'`  \
        R CMD SHLIB dd.rcpp.cpp
    

    建立一个共享库。我们使用Rscript(或r)向Rcpp询问它的头文件和库位置。构建完成后,我们可以从 R 中加载并使用它,如下所示:

    dyn.load("dd.rcpp.so")
    
    dd.rcpp <- function() {
        x <- integer(10000)
        res <- .Call("dd_rcpp", x)
        tabulate(res$vec)
    }
    

    以同样的方式,您可以轻松地向后发送各种 R 和 C++ 数据类型的向量、矩阵等。希望这会有所帮助。

    编辑 2(大约五年后):

    所以这个答案刚刚获得了赞成票,因此在我的队列中冒泡了。 很多时间已经过去了,而 Rcpp 的功能已经很多丰富了。所以我很快就写了这篇

    #include <Rcpp.h>
    
    // [[Rcpp::export]]
    Rcpp::IntegerVector dd2(Rcpp::IntegerVector vec) {
        int n = vec.size(), i = 0;
        if (n != 10000) 
            throw std::length_error("Wrong vector size");
        for (int a = 0; a < 9; a++)
            for (int b = 0; b < 9; b++)
                for (int c = 0; c < 9; c++)
                    for (int d = 0; d < 9; d++)
                        vec(i++) = a*b - c*d;
        return vec;
    }
    
    /*** R
    x <- integer(10000)
    tabulate( dd2(x) )
    */
    

    可以与文件/tmp/dd.cpp中的代码如下使用

    R> Rcpp::sourceCpp("/tmp/dd.cpp")    # on from any other file and path
    
    R> x <- integer(10000)
    
    R> tabulate( dd2(x) )
     [1]  87 132 105 155  93 158  91 161  72 104  45 147  41  96
    [15]  72 120  36  90  32  87  67  42  26 120  41  36  27  75
    [29]  20  62  16  69  19  28  49  45  12  18  11  57  14  48
    [43]  10  18   7  12   6  46  23  10   4  10   4   6   3  38
    [57]   2   4   2   3   2   2   1  17
    R> 
    

    一些主要区别是:

    • 更简单的构建:只需sourceCpp()它;甚至在最后执行 R 测试代码
    • 成熟的IntegerVector 类型
    • sourceCpp() 代码生成器自动添加的异常处理包装器

    【讨论】:

    • 感谢您的回答!您能否阐明 RcppTemplate 中“有趣的新代码”的含义。该软件包与 Rcpp 有何不同?我对 C++ 几乎一无所知,这些包是如何工作的?
    • Dirk,也许是时候讨论一下让 C++ 与 R 一起工作的快速小插曲了...:(!
    • 很难解释它们的不同之处,因为它们在 C++ 中的不同之处。
    • 很公平,我只是想弄清楚为什么人们会选择其中之一。我正在尝试查看“使用 R 的 HPC 简介”,看看它是否有更多详细信息,但您的网站似乎已关闭。
    • 立即尝试——断电后服务器没有自动服务器。至于“选哪一个”,何不开始一个简单的测试项目,两个都试试?
    猜你喜欢
    • 1970-01-01
    • 2012-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多