vignettes/proposal.Rmd
proposal.Rmd
The three rules of software optimization are:
This proposal discusses simplifying the application of these rules for R code that calls into native code.
R has excellent facilities for profiling R code: the main entry point is the Rprof()
function that starts an execution mode where the R call stack is sampled periodically, optionally at source line level, and written to a file. Memory usage can also be collected. The results of a profiling run can be analyzed with summaryRprof()
, or visualized using the profvis
, aprof
, or GUIProfiler
packages.
With Rprof()
, the execution time of native code is only available as a bulk, without detailed source information. Conversely, when using a native code profiler such as gperftools
or callgrind
, the resulting profile does not contain any link to the original R source code. The same applies to memory usage information.
This project aims at bridging this gap with a drop-in replacement to Rprof()
that records call stacks and memory usage information at both R and native levels, and later commingles them to present a unified view to the user. This drop-in replacement can be designed with support for multiple processes or threads in mind, another shortcoming of the current implementation of R’s profiler.
Calling native code from R is done mostly for one or both of the following reasons:
RCurl
and curl
wrap libcurlRSQLite
and RMySQL
usually wrap native librarieshaven
wraps ReadStat, which reads foreign data formatsRcppCCTZ
wraps CCTZ, a modern library for handling time zonesRcppTOML
wraps cpptoml, a library to parse configuration files in the TOML languagerandomForest
has C and Fortran code at its coreMatrix
tibble
readr
and data.table
dplyr
and data.table
igraph
Calls to native code from R packages are widespread: More than 500 CRAN packages consist mostly of C code, according to a GitHub search. The Rcpp
package, which makes it trivial to embed C++ code in a package, is currently used by over 900 CRAN packages. Furthermore, it is very easy to create a one-off C++ function for use in an analysis script with the help of Rcpp::cppFunction()
.
Improved profiling facilities will greatly simplify the analysis and elimination of run time and memory bottlenecks in such code.1 Over time, faster and less memory-hungry implementations will save computational resources or allow tackling larger problems.
A joint R/native profiler can be implemented based on the following outline:
The jointprof
package, available on GitHub at https://github.com/r-prof/jointprof, is a working proof of concept (currently only 64-bit Ubuntu Linux) for the seemingly tricky first step.2 No change to base R is necessary to achieve this. As a result, a data frame of samples is returned where each row contains both R and native stack traces (separately in two columns).
The native stack traces are currently generated using the gperftools
library because it was easiest to get started with. The ultimate choice of native profiler will depend on other factors, such as platform interoperability (in particular Windows compatibility) and ease of integration. Regardless of this choice, the joint R/native profiler will create an .Rprof
file compatible with those created by Rprof()
, so that existing tools in the R ecosystem can be used to further analyze and visualize profiling results. For interoperability, the proftools
package already offers a way to convert an .Rprof
file to the format created by callgrind
and understood by a number of tools outside the R ecosystem.
The project includes the development of three R packages, see also the jointprof
issue tracker on GitHub:
Rprof()
drop-in replacement, based on the technique demonstrated in jointprof
.
*.prof
files for gperftools
).Splitting the work in smaller packages helps code reuse and allows better testing of the individual components. In particular, the I/O package can then be used by other packages or code without having to include a potentially heavy dependency. The code and intermediate format currently used by the profvis
package can be used as a starting point, its maintainer Winston Chang has indicated consent.
Best practices, such as version control, continuous integration, and clean code, will be used throughout the project to ensure quality and maintainability.
The project’s timeline corresponds to a full-time workload and does not reflect actual completion times. I expect to complete the project in roughly four months after acceptance, and to present final results at useR!2017 in Brussels.
Even with the encouraging results shown in the proof of concept, some open questions can only be answered as part of the project:
gperftools
or other sampling profilers can be used successfully on non-Linux operating systems, in particular on Windows. Reasonable efforts will be made to establish cross-platform compatibility, however in the worst case support for particular platforms may be restricted, and users will be instructed to use virtualization solutions.Rcpp::cppFunction()
, may not be fully supported.I expect to finish the “Improving DBI” project, funded by the ISC, by end of April. This project, if awarded, can then start right after that. I have submitted another proposal to the ISC, “Establishing DBI”. Should both projects be accepted, I still expect to adhere to the estimated completion times.
I would like to ask for financial support for the implementation of the project and for attending one conference to present the results. Only open-source libraries will be considered, licensing fees are not an issue.
Based on the timeline and the feature description, I would like to ask for USD 10’000 to cover the implementation costs. I will provide the equipment, backup funds are available to cover potential costs for replacement/repair. It would be great to present the work at useR!2017, I would also like to ask for reimbursement of the conference fee, accommodation, and transportation (approx. USD 1’000).
The work will be continuously uploaded to GitHub, tested on r-hub, Travis-CI, and AppVeyor, and released to CRAN at the end of the project. GitHub seems to be very popular in the R community, its pull request mechanism allows contribution and code review in a very simple and convenient way. Maintainers of related packages (profvis
, aprof
, proftools
, GUIProfiler
, …) will be contacted and involved in a very early stage of the project. Design questions will be discussed on GitHub.
The newly implemented packages will be licensed under the MIT license to simplify their use for commercial purposes, except where linked or imported code requires a different licensing model.
I would like to thank Hadley Wickham, who suggested to investigate joint profiling of R and native code, and also to Romain François who both gave feedback on an earlier version of the proposal. Thanks also to Winston Chang for supporting the extraction of code from his profvis
package.
For example, with a performance regression in a development version of RSQLite
, the R profiling results seemed inconclusive, because the extra run time was being spent during the evaluation of a promise. The C++ profiling results have indicated that time was spent evaluating R code, but of course not which particular R code. With unified profiling data, the problem would have been spotted much easier.↩︎
Technically, it works by calling Rprof()
, which installs a SIGPROF
signal handler, and then replacing this handler by one that captures the native stack trace (using the gperftools
library) and then records the R stack trace (by invoking the handler originally installed by Rprof()
).↩︎