我有package1,通过RcppArmadillo :: RcppArmadillo.package.skeleton()创建.包的唯一源文件是package1 / src / shared.cpp,它包含一个使用RcppArmadillo计算矩阵列总和的函数.因此,shared.cpp的源代码如下:
//[[Rcpp::depends(RcppArmadillo)]]
//[[Rcpp::interfaces(r, cpp)]]
#include "RcppArmadillo.h"
// [[Rcpp::export]]
arma::vec col_sums(const arma::mat& matty){
return arma::sum(matty, 0).t();
}
现在假设我想在另一个名为package2的包中回收此函数.我是通过在DESCRIPTION中编辑Imports和LinkingTo,添加package1来实现的.然后,这个新包的唯一源文件是package2 / src / testimport.cpp
//[[Rcpp::depends(RcppArmadillo, package1)]]
#include "RcppArmadillo.h"
#include "package1.h"
//[[Rcpp::export]]
arma::vec col_sums(const arma::mat& test){
return arma::sum(test,0).t();
}
//[[Rcpp::export]]
arma::vec col_sums_imported(const arma::mat& test){
return package1::col_sums(test);
}
现在如果我编译两个包,并对3 1函数进行基准测试,我得到了
library(magrittr)
library(rbenchmark)
nr <- 100
p <- 800
testmat <- rnorm(nr * p) %>% matrix(ncol=p)
benchmark(package2::col_sums(testmat),
package2::col_sums_imported(testmat),
colSums(testmat),
package1::col_sums(testmat),
replications=1000)
我希望package1 :: col_sums和package2 :: col_sums之间没有任何区别,但是这两者和package2 :: col_sums_imported之间的差别很小或很小,它们使用cpp接口从package2调用package1 :: col_sums.
相反,我得到了(我也添加了R的colSums进行比较)
test replications elapsed relative user.self sys.self user.child sys.child 3 colSums(testmat) 1000 0.050 1.429 0.052 0.000 0 0 4 package1::col_sums(testmat) 1000 0.035 1.000 0.036 0.000 0 0 1 package2::col_sums(testmat) 1000 0.038 1.086 0.036 0.000 0 0 2 package2::col_sums_imported(testmat) 1000 0.214 6.114 0.100 0.108 0 0
这个6倍减速让我困惑,因为我没想到会有这样的差异.将“共享”函数的来源复制到新包中是否优先,为什么?我觉得只有一个col_sums源可以让我更容易地在两个包中传播更改.或者还有另外一个原因导致我的代码变慢了吗?
编辑:除了@ duckmayr的答案之外,我还更新了我的最小github包示例,以显示如何在package1中使用用户创建的函数,导出到其他包,导入到package2.代码可以在https://github.com/mkln/rcppeztest找到
正如其他人所提到的,允许其他包从C调用您的C代码需要在inst / include /中使用头文件. Rcpp :: interfaces允许您自动创建此类文件.但是,正如我在下面演示的那样,手动创建自己的标头可以缩短执行时间.我相信这是因为依靠Rcpp :: interfaces为您创建标题可能会导致更复杂的标题代码.在我进一步展示一个“更简单”的方法以缩短执行时间之前,我需要注意的是,虽然这对我有效(并且我已经使用过这种方法,我将在下面多次演示而没有问题),但是“更复杂” Rcpp :: interfaces采用的方法部分用于与Section 5.4.3. of the Writing R Extensions manual中的语句进行比较.(具体来说,与R_GetCCallable有关的位你将在下面看到).因此,使用我提供的代码来改善您的执行时间,这是您自己的危险.1,2
共享col_sums代码的简单标头可能如下所示:
#ifndef RCPP_package3
#define RCPP_package3
#include <RcppArmadillo.h>
namespace package3 {
inline arma::vec col_sums(const arma::mat& test){
return arma::sum(test,0).t();
}
}
#endif
但是,由Rcpp :: interfaces创建的标头如下所示:
// Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#ifndef RCPP_package1_RCPPEXPORTS_H_GEN_
#define RCPP_package1_RCPPEXPORTS_H_GEN_
#include <RcppArmadillo.h>
#include <Rcpp.h>
namespace package1 {
using namespace Rcpp;
namespace {
void validateSignature(const char* sig) {
Rcpp::Function require = Rcpp::Environment::base_env()["require"];
require("package1", Rcpp::Named("quietly") = true);
typedef int(*Ptr_validate)(const char*);
static Ptr_validate p_validate = (Ptr_validate)
R_GetCCallable("package1", "_package1_RcppExport_validate");
if (!p_validate(sig)) {
throw Rcpp::function_not_exported(
"C++ function with signature '" + std::string(sig) + "' not found in package1");
}
}
}
inline arma::vec col_sums(const arma::mat& matty) {
typedef SEXP(*Ptr_col_sums)(SEXP);
static Ptr_col_sums p_col_sums = NULL;
if (p_col_sums == NULL) {
validateSignature("arma::vec(*col_sums)(const arma::mat&)");
p_col_sums = (Ptr_col_sums)R_GetCCallable("package1", "_package1_col_sums");
}
RObject rcpp_result_gen;
{
RNGScope RCPP_rngScope_gen;
rcpp_result_gen = p_col_sums(Shield<SEXP>(Rcpp::wrap(matty)));
}
if (rcpp_result_gen.inherits("interrupted-error"))
throw Rcpp::internal::InterruptedException();
if (Rcpp::internal::isLongjumpSentinel(rcpp_result_gen))
throw Rcpp::LongjumpException(rcpp_result_gen);
if (rcpp_result_gen.inherits("try-error"))
throw Rcpp::exception(Rcpp::as<std::string>(rcpp_result_gen).c_str());
return Rcpp::as<arma::vec >(rcpp_result_gen);
}
}
#endif // RCPP_package1_RCPPEXPORTS_H_GEN_
所以,我通过创建了两个额外的包
library(RcppArmadillo) RcppArmadillo.package.skeleton(name = "package3", example_code = FALSE) RcppArmadillo.package.skeleton(name = "package4", example_code = FALSE)
然后在package3 / inst / include中,我添加了包含上面“简单头”代码的package3.h(我还在src /中添加了一个一次性的“Hello World”cpp文件).在package4 / src /中添加了以下内容:
#include <package3.h>
// [[Rcpp::export]]
arma::vec col_sums(const arma::mat& test){
return arma::sum(test,0).t();
}
// [[Rcpp::export]]
arma::vec simple_header_import(const arma::mat& test){
return package3::col_sums(test);
}
以及在DESCRIPTION文件中将package3添加到LinkingTo.
然后,在安装新软件包之后,我对所有函数进行了基准测试:
library(rbenchmark)
set.seed(1)
nr <- 100
p <- 800
testmat <- matrix(rnorm(nr * p), ncol = p)
benchmark(original = package1::col_sums(testmat),
first_copy = package2::col_sums(testmat),
complicated_import = package2::col_sums_imported(testmat),
second_copy = package4::col_sums(testmat),
simple_import = package4::simple_header_import(testmat),
replications = 1e3,
columns = c("test", "relative", "elapsed", "user.self", "sys.self"),
order = "relative")
test relative elapsed user.self sys.self
2 first_copy 1.000 0.174 0.174 0.000
4 second_copy 1.000 0.174 0.173 0.000
5 simple_import 1.000 0.174 0.174 0.000
1 original 1.126 0.196 0.197 0.000
3 complicated_import 6.690 1.164 0.544 0.613
虽然更“复杂”的标题功能慢了6倍,但“更简单”的标题功能却没有.
1.但是,Rcpp :: interfaces生成的自动代码确实包含了一些在R_GetCCallable问题旁边可能是多余的功能,尽管它们可能是必需的,并且在某些其他必要的上下文中也是如此.
2.注册函数始终是可移植的,并且通过Writing R Extensions手册指示包作者这样做,但是对于内部/组织/等.使用我相信如果涉及的所有包都是从源构建的,那么这里介绍的方法应该有用.有关讨论,请参阅this section of Hadley Wickham’s R Packages以及上面链接的Writing R Extensions手册部分.
