我注意到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
知道我哪里做错了吗
1条答案
按热度按时间cygmwpex1#
标杆管理是一个很好的工具。我们习惯于确定应该工作的事情,确实工作。有时候,当他们误入歧途时,他们让我们想知道为什么。
在与Rcpp合作了这么多年之后,你最初发现的“30+”倍的差异是不可信的。鉴于你没有提供下载,我近似如下:
cppFunction
pkgKitten::kitten()
创建的快速包,为RcppEigen包设置参数;然后我把函数复制到结果如下,代码进一步下降
结果
没有你所期望的真实的区别。
代码
您的适应功能
我将这个包命名为
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文件。
有了这些,我再一次得到了你所期望的可比结果(中位数)。也就是说,在基准测试过程中,垃圾收集启动时,你总是会得到一个随机的尾部,即使只有一次。