当前位置 : 主页 > 网络安全 > 测试自动化 >

包含从其他Rcpp包导出的代码时性能下降

来源:互联网 收集:自由互联 发布时间:2021-06-22
我最近创建了一个包,并希望回收我在新包中为它编写的许多“引擎盖下”函数.但是,在第一次尝试中,我发现在将cpp代码导入新包时会显着降低性能.我将在下面澄清. 我有package1,通过Rc
我最近创建了一个包,并希望回收我在新包中为它编写的许多“引擎盖下”函数.但是,在第一次尝试中,我发现在将cpp代码导入新包时会显着降低性能.我将在下面澄清.

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

网友评论