I have recently created a package and would like to recycle many of the "under-the-hood" functions I wrote for it in a new package. However, in a first try I have found that there's a significant performance decrease when importing cpp code into the new package. I will clarify below.
I have package1, created via RcppArmadillo::RcppArmadillo.package.skeleton(). The package's only source file is package1/src/shared.cpp, which includes a function that calculates column sums of a matrix using RcppArmadillo. The source code for shared.cpp is thus:
//[[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();
}
Now suppose I want to recycle this function in another package, called package2. I do so by editing Imports and LinkingTo in DESCRIPTION, adding package1. Then, the only source file for this new package is 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);
}
Now if I compile both packages, and benchmark the 3+1 functions, I get
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)
I would expect there to be no difference at all between package1::col_sums and package2::col_sums, but also a minimal or small difference between those two and package2::col_sums_imported, which calls package1::col_sums from package2 using the cpp interface.
Instead I get (I also added R's colSums for a comparison)
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
This 6x slow-down puzzles me, because I was not expecting there to be such a difference. Is it preferrable to just copy the source of the "shared" functions to the new package, and why? I feel like having only one source for col_sums would allow me to more easily propagate changes across both packages. Or is there another reason why my code slows down so much?
EDIT: in addition to @duckmayr's answer below, I have updated my minimal github package example to show how a user-created function can be used in package1, exported to other packages, imported to package2. Code can be found at https://github.com/mkln/rcppeztest