我有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手册部分.