c++ 预编译R包中的Stark Rcpp性能差异,并使用cppFunction()本地获取

siv3szwd  于 2023-07-01  发布在  其他
关注(0)|答案(1)|浏览(183)

我注意到Rcpp在预编译的R包中的性能与在本地使用动态cppFunction(<source code>)时的性能之间存在巨大差异。
这里是一个最小的可重复的例子。在运行roxygen之前,包目录结构如下所示:

其中的文件如下所示:
DESCRIPTION

Package: bare
Type: Package
Title: Bare Example
Date: 2023-06-29
Version: 0.1
Authors@R: c(
    person("Adam", "Kapelner", email = "kapelner@qc.cuny.edu", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5985-6792"))
  )
License: GPL-3
Description: Provides a bare example of performance issues described in the stackoverflow question
Encoding: UTF-8
Depends: R (>= 4.0.0)
Imports: Rcpp, RcppEigen, checkmate, stats
LinkingTo: Rcpp, RcppEigen

code.R

#' A faster-than-base determinant function
#' 
#' Via the eigen package
#' 
#' @param X                 A numeric matrix of size p x p
#' @param num_cores         The number of cores to use
#' 
#' @return                  The determinant as a scalar numeric value
#' 
#' @export
eigen_det = function(X, num_cores = 1){
    eigen_det_cpp(X, num_cores) 
}

routine.cpp

#include <Rcpp.h>
#include <RcppEigen.h>
#include <omp.h>
using namespace Rcpp;

// [[Rcpp::export]]
double eigen_det_cpp(const Eigen::Map<Eigen::MatrixXd> X, int n_cores) {
  Eigen::setNbThreads(n_cores);
  return X.determinant();
}

运行roxygenise后,目录结构如下所示:

其中,新的自动生成的文件如下所示:
NAMESPACE

# Generated by roxygen2: do not edit by hand

export(eigen_det)

useDynLib("bare", .registration=TRUE)

在这里我手工添加了useDynLib行。
RcppExports.R

# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

eigen_det_cpp <- function(X, n_cores) {
    .Call(`_bare_eigen_det_cpp`, X, n_cores)
}

RcppExports.cpp

// Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

#include <RcppEigen.h>
#include <Rcpp.h>

using namespace Rcpp;

#ifdef RCPP_USE_GLOBAL_ROSTREAM
Rcpp::Rostream<true>&  Rcpp::Rcout = Rcpp::Rcpp_cout_get();
Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
#endif

// eigen_det_cpp
double eigen_det_cpp(const Eigen::Map<Eigen::MatrixXd> X, int n_cores);
RcppExport SEXP _bare_eigen_det_cpp(SEXP XSEXP, SEXP n_coresSEXP) {
BEGIN_RCPP
    Rcpp::RObject rcpp_result_gen;
    Rcpp::RNGScope rcpp_rngScope_gen;
    Rcpp::traits::input_parameter< const Eigen::Map<Eigen::MatrixXd> >::type X(XSEXP);
    Rcpp::traits::input_parameter< int >::type n_cores(n_coresSEXP);
    rcpp_result_gen = Rcpp::wrap(eigen_det_cpp(X, n_cores));
    return rcpp_result_gen;
END_RCPP
}

static const R_CallMethodDef CallEntries[] = {
    {"_bare_eigen_det_cpp", (DL_FUNC) &_bare_eigen_det_cpp, 2},
    {NULL, NULL, 0}
};

RcppExport void R_init_bare(DllInfo *dll) {
    R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
    R_useDynamicSymbols(dll, FALSE);
}

现在我运行R CMD INSTALL并使用以下代码来测试性能:

library(microbenchmark)
library(bare)

p = 100
M = matrix(rnorm(p^2), nrow = p)

Rcpp::cppFunction(depends = "RcppEigen", '                  
    double eigen_det_cpp_on_the_fly(const Eigen::Map<Eigen::MatrixXd> X, int n_cores) {
        Eigen::setNbThreads(n_cores);
        return X.determinant();
    }
')

microbenchmark(
  within_package =  eigen_det(M, 1),
  on_the_fly =      eigen_det_cpp_on_the_fly(M, 1),
  times = 10
)

这导致:

Unit: microseconds
           expr    min     lq    mean median     uq    max neval
 within_package 3210.1 3218.7 3318.54 3268.5 3323.7 3712.0    10
     on_the_fly   98.0  102.1  200.30  102.7  106.3 1039.4    10

这意味着有一些开销被添加到包Rcpp例程中,如果您通过cppFunction动态编译代码,这些开销不会被添加。
一些设置信息:

> sessionInfo()
R Under development (unstable) (2023-06-17 r84564 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 22621)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8 LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] bare_0.1              microbenchmark_1.4.10

loaded via a namespace (and not attached):
[1] compiler_4.4.0      RcppEigen_0.3.3.9.3 Matrix_1.5-4.1      tools_4.4.0         Rcpp_1.0.10        
[6] grid_4.4.0          lattice_0.21-8

知道我哪里做错了吗

cygmwpex

cygmwpex1#

标杆管理是一个很好的工具。我们习惯于确定应该工作的事情,确实工作。有时候,当他们误入歧途时,他们让我们想知道为什么。
在与Rcpp合作了这么多年之后,你最初发现的“30+”倍的差异是不可信的。鉴于你没有提供下载,我近似如下:

  • 测试脚本,包括cppFunction
  • 通过pkgKitten::kitten()创建的快速包,为RcppEigen包设置参数;然后我把函数复制到

结果如下,代码进一步下降

结果

没有你所期望的真实的区别。

> source("question.R")
Unit: microseconds
           expr    min     lq    mean  median     uq     max neval cld
 within_package 89.319 89.837 104.292 90.8560 92.759 224.150    10   a
     on_the_fly 88.853 89.160 164.197 89.5085 90.854 752.128    10   a
>
代码

您的适应功能

p <- 100
M <- matrix(rnorm(p^2), nrow = p)

Rcpp::cppFunction(depends = "RcppEigen", '
    double eigen_det_cpp_cppfunction(const Eigen::Map<Eigen::MatrixXd> X, int n_cores) {
        Eigen::setNbThreads(n_cores);
        return X.determinant();
    }
')

.libPaths("lib")
library(testPkg)
library(microbenchmark)

res <- microbenchmark(within_package =  eigen_det_cpp(M, 1),
                      on_the_fly =      eigen_det_cpp_cppfunction(M, 1),
                      times = 10)
print(res)

我将这个包命名为testPkg,并将其安装在一个ad-hoc目录中。这些都不重要但是现在我直接调用了ad-hoc和package函数(重命名了ad-hoc函数)。这应该避免“谁叫谁”的混淆。

repo发布后编辑

感谢您链接到repo。我做了一些小的改动:
1.向C文件添加基础结构,以a)加载动态库,进行符号注册,并导出函数。即我添加了
//'@ useDynLib bare,.registration=TRUE //'@ export
以及RANx 1 m3n1x和新鲜的roxygen运行。
1.确保直接调用C
函数。为了确定,我还在基准测试中添加了R Package 器。但我们为什么要调用额外的 Package 呢?你的“飞行变体”没有这个负担。
1.移动函数的 invariant 线程设置器。我们在一开始就认为这样的事情是可能的。但不在函数内部。
1.为了完整性,请从存储库中删除.dll和.o文件。
有了这些,我再一次得到了你所期望的可比结果(中位数)。也就是说,在基准测试过程中,垃圾收集启动时,你总是会得到一个随机的尾部,即使只有一次。

edd@rob:/tmp/rcpp/bare_example_Rcpp(main)$ Rscript testing.R 
Unit: milliseconds
             expr     min      lq    mean  median      uq      max neval cld
   within_package 1.45189 1.50928 1.56683 1.55025 1.60361  1.74318    10   a
 within_package_R 1.45703 1.48958 1.52239 1.50439 1.56312  1.60523    10   a
       on_the_fly 1.44407 1.44756 2.62651 1.54337 1.64998 12.40928    10   a
edd@rob:/tmp/rcpp/bare_example_Rcpp(main)$

相关问题