Skip to content

Commit cf4c162

Browse files
authored
Merge pull request #504 from RcppCore/feature/source-cpp-cache-dir
Enable caching of sourceCpp compilations across R sessions
2 parents cf6cfc8 + a06980d commit cf4c162

File tree

8 files changed

+336
-125
lines changed

8 files changed

+336
-125
lines changed

ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
2016-07-11 J.J. Allaire <[email protected]>
2+
3+
* R/Attributes.R Add cacheDir argument to sourceCpp to enable caching of
4+
shared libraries across R sessions
5+
* src/attributes.cpp Add cacheDir argument to sourceCpp to enable caching of
6+
shared libraries across R sessions
7+
18
2016-07-03 Dirk Eddelbuettel <[email protected]>
29

310
* DESCRIPTION (Version): Rolling minor version

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: Rcpp
22
Title: Seamless R and C++ Integration
3-
Version: 0.12.5.4
3+
Version: 0.12.5.7
44
Date: 2016-07-03
55
Author: Dirk Eddelbuettel, Romain Francois, JJ Allaire, Kevin Ushey,
66
Qiang Kou, Douglas Bates and John Chambers

R/Attributes.R

Lines changed: 134 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,24 @@ sourceCpp <- function(file = "",
2323
env = globalenv(),
2424
embeddedR = TRUE,
2525
rebuild = FALSE,
26+
cacheDir = getOption("rcpp.cache.dir", tempdir()),
27+
cleanupCacheDir = FALSE,
2628
showOutput = verbose,
2729
verbose = getOption("verbose"),
2830
dryRun = FALSE) {
2931

32+
# use an architecture/version specific subdirectory of the cacheDir
33+
# (since cached dynlibs can now perist across sessions we need to be
34+
# sure to invalidate them when R or Rcpp versions change)
35+
cacheDir <- path.expand(cacheDir)
36+
cacheDir <- .sourceCppPlatformCacheDir(cacheDir)
37+
cacheDir <- normalizePath(cacheDir)
38+
3039
# resolve code into a file if necessary. also track the working
3140
# directory to source the R embedded code chunk within
3241
if (!missing(code)) {
3342
rWorkingDir <- getwd()
34-
file <- tempfile(fileext = ".cpp")
43+
file <- tempfile(fileext = ".cpp", tmpdir = cacheDir)
3544
con <- file(file, open = "w")
3645
writeLines(code, con)
3746
close(con)
@@ -55,10 +64,10 @@ sourceCpp <- function(file = "",
5564
"is not permitted.")
5665
}
5766
}
58-
67+
5968
# get the context (does code generation as necessary)
6069
context <- .Call("sourceCppContext", PACKAGE="Rcpp",
61-
file, code, rebuild, .Platform)
70+
file, code, rebuild, cacheDir, .Platform)
6271

6372
# perform a build if necessary
6473
if (context$buildRequired || rebuild) {
@@ -105,7 +114,7 @@ sourceCpp <- function(file = "",
105114

106115
# unload and delete existing dylib if necessary
107116
if (file.exists(context$previousDynlibPath)) {
108-
try(silent=T, dyn.unload(context$previousDynlibPath))
117+
try(silent=TRUE, dyn.unload(context$previousDynlibPath))
109118
file.remove(context$previousDynlibPath)
110119
}
111120

@@ -189,10 +198,37 @@ sourceCpp <- function(file = "",
189198
setwd(rWorkingDir) # will be reset by previous on.exit handler
190199
source(file=srcConn, echo=TRUE)
191200
}
201+
202+
# cleanup the cache dir if requested
203+
if (cleanupCacheDir)
204+
cleanupSourceCppCache(cacheDir, context$cppSourcePath, context$buildDirectory)
192205

193206
# return (invisibly) a list containing exported functions and modules
194207
invisible(list(functions = context$exportedFunctions,
195-
modules = context$modules))
208+
modules = context$modules,
209+
cppSourcePath = context$cppSourcePath,
210+
buildDirectory = context$buildDirectory))
211+
}
212+
213+
214+
# Cleanup a directory used as the cache for a sourceCpp compilation. This will
215+
# remove all files from the cache directory that aren't a result of the
216+
# compilation that yielded the passed buildDirectory.
217+
cleanupSourceCppCache <- function(cacheDir, cppSourcePath, buildDirectory) {
218+
# normalize cpp source path and build directory
219+
cppSourcePath <- normalizePath(cppSourcePath)
220+
buildDirectory <- normalizePath(buildDirectory)
221+
222+
# determine the parent dir that was used for the compilation then collect all the
223+
# *.cpp files and subdirectories therein
224+
cacheFiles <- list.files(cacheDir, pattern = glob2rx("*.cpp"), recursive = FALSE, full.names = TRUE)
225+
cacheFiles <- c(cacheFiles, list.dirs(cacheDir, recursive = FALSE, full.names = TRUE))
226+
cacheFiles <- normalizePath(cacheFiles)
227+
228+
# determine the list of tiles that were not yielded by the passed sourceCpp
229+
# result and remove them
230+
oldCacheFiles <- cacheFiles[!cacheFiles %in% c(cppSourcePath, buildDirectory)]
231+
unlink(oldCacheFiles, recursive = TRUE)
196232
}
197233

198234
# Define a single C++ function
@@ -202,6 +238,7 @@ cppFunction <- function(code,
202238
includes = character(),
203239
env = parent.frame(),
204240
rebuild = FALSE,
241+
cacheDir = getOption("rcpp.cache.dir", tempdir()),
205242
showOutput = verbose,
206243
verbose = getOption("verbose")) {
207244

@@ -258,6 +295,7 @@ cppFunction <- function(code,
258295
exported <- sourceCpp(code = code,
259296
env = env,
260297
rebuild = rebuild,
298+
cacheDir = cacheDir,
261299
showOutput = showOutput,
262300
verbose = verbose)
263301

@@ -304,6 +342,7 @@ evalCpp <- function(code,
304342
plugins = character(),
305343
includes = character(),
306344
rebuild = FALSE,
345+
cacheDir = getOption("rcpp.cache.dir", tempdir()),
307346
showOutput = verbose,
308347
verbose = getOption( "verbose" ) ){
309348

@@ -312,7 +351,7 @@ evalCpp <- function(code,
312351
env <- new.env()
313352
cppFunction(code, depends = depends, plugins = plugins,
314353
includes = includes, env = env,
315-
rebuild = rebuild, showOutput = showOutput, verbose = verbose )
354+
rebuild = rebuild, cacheDir = cacheDir, showOutput = showOutput, verbose = verbose )
316355
fun <- env[["get_value"]]
317356
fun()
318357
}
@@ -989,3 +1028,92 @@ sourceCppFunction <- function(func, isVoid, dll, symbol) {
9891028
}
9901029
.hasDevelTools
9911030
}
1031+
1032+
1033+
# insert a dynlib entry into the cache
1034+
.sourceCppDynlibInsert <- function(cacheDir, file, code, dynlib) {
1035+
cache <- .sourceCppDynlibReadCache(cacheDir)
1036+
index <- .sourceCppFindCacheEntryIndex(cache, file, code)
1037+
if (is.null(index))
1038+
index <- length(cache) + 1
1039+
cache[[index]] <- list(file = file, code = code, dynlib = dynlib)
1040+
.sourceCppDynlibWriteCache(cacheDir, cache)
1041+
}
1042+
1043+
# attempt to lookup a dynlib entry from the cache
1044+
.sourceCppDynlibLookup <- function(cacheDir, file, code) {
1045+
cache <- .sourceCppDynlibReadCache(cacheDir)
1046+
index <- .sourceCppFindCacheEntryIndex(cache, file, code)
1047+
if (!is.null(index))
1048+
cache[[index]]$dynlib
1049+
else
1050+
list()
1051+
}
1052+
1053+
# write the cache to disk
1054+
.sourceCppDynlibWriteCache <- function(cacheDir, cache) {
1055+
index_file <- file.path(cacheDir, "cache.rds")
1056+
save(cache, file = index_file, compress = FALSE)
1057+
}
1058+
1059+
# read the cache from disk
1060+
.sourceCppDynlibReadCache <- function(cacheDir) {
1061+
index_file <- file.path(cacheDir, "cache.rds")
1062+
if (file.exists(index_file)) {
1063+
load(file = index_file)
1064+
get("cache")
1065+
} else {
1066+
list()
1067+
}
1068+
}
1069+
1070+
# search the cache for an entry that matches the file or code argument
1071+
.sourceCppFindCacheEntryIndex <- function(cache, file, code) {
1072+
1073+
if (length(cache) > 0) {
1074+
for (i in 1:length(cache)) {
1075+
entry <- cache[[i]]
1076+
if ((nzchar(file) && identical(file, entry$file)) ||
1077+
(nzchar(code) && identical(code, entry$code))) {
1078+
if (file.exists(entry$dynlib$cppSourcePath))
1079+
return(i)
1080+
}
1081+
}
1082+
}
1083+
1084+
# none found
1085+
NULL
1086+
}
1087+
1088+
# generate an R version / Rcpp version specific cache dir for dynlibs
1089+
.sourceCppPlatformCacheDir <- function(cacheDir) {
1090+
1091+
dir <- file.path(cacheDir,
1092+
paste(R.version$platform,
1093+
utils::packageVersion("Rcpp"),
1094+
sep = "-"))
1095+
if (!dir.exists(dir))
1096+
dir.create(dir, recursive = TRUE)
1097+
1098+
dir
1099+
}
1100+
1101+
# generate a unique token for a cacheDir
1102+
.sourceCppDynlibUniqueToken <- function(cacheDir) {
1103+
# read existing token (or create a new one)
1104+
token_file <- file.path(cacheDir, "token.rds")
1105+
if (file.exists(token_file))
1106+
load(file = token_file)
1107+
else
1108+
token <- 0
1109+
1110+
# increment
1111+
token <- token + 1
1112+
1113+
# write it
1114+
save(token, file = token_file)
1115+
1116+
# return it as a string
1117+
as.character(token)
1118+
}
1119+

inst/NEWS.Rd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
}
3434
\item Changes in Rcpp Attributes:
3535
\itemize{
36+
\item Add \code{cacheDir} argument to \code{sourceCpp()} to enable caching of
37+
shared libraries across R sessions.
3638
\item Code generation now deals correctly which packages containing a dot
3739
in their name (Qiang in \ghpr{501} fixing \ghit{500})
3840
}

man/cppFunction.Rd

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ Define an R Function with a C++ Implementation
77
Dynamically define an R function with C++ source code. Compiles and links a shared library with bindings to the C++ function then defines an R function that uses \code{.Call} to invoke the library.
88
}
99
\usage{
10-
cppFunction(code, depends = character(), plugins = character(),
11-
includes = character(), env = parent.frame(), rebuild = FALSE,
10+
cppFunction(code, depends = character(), plugins = character(), includes = character(),
11+
env = parent.frame(), rebuild = FALSE, cacheDir = getOption("rcpp.cache.dir", tempdir()),
1212
showOutput = verbose, verbose = getOption("verbose"))
1313
}
1414

@@ -30,6 +30,9 @@ cppFunction(code, depends = character(), plugins = character(),
3030
}
3131
\item{rebuild}{
3232
Force a rebuild of the shared library.
33+
}
34+
\item{cacheDir}{
35+
Directory to use for caching shared libraries. If the underlying code passed to \code{sourceCpp} has not changed since the last invocation then a cached version of the shared library is used. The default value of \code{tempdir()} results in the cache being valid only for the current R session. Pass an alternate directory to preserve the cache across R sessions.
3336
}
3437
\item{showOutput}{
3538
\code{TRUE} to print \code{R CMD SHLIB} output to the console.

man/evalCpp.Rd

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ Evaluates a C++ expression. This creates a C++ function using
99
\code{\link{cppFunction}} and calls it to get the result.
1010
}
1111
\usage{
12-
evalCpp(code, depends = character(), plugins = character(),
13-
includes = character(), rebuild = FALSE, showOutput = verbose,
14-
verbose = getOption("verbose"))
12+
evalCpp(code, depends = character(), plugins = character(), includes = character(),
13+
rebuild = FALSE, cacheDir = getOption("rcpp.cache.dir", tempdir()),
14+
showOutput = verbose, verbose = getOption("verbose"))
15+
1516
areMacrosDefined(names, depends = character(), includes = character(),
1617
rebuild = FALSE, showOutput = verbose,
1718
verbose = getOption("verbose"))
@@ -34,6 +35,9 @@ see \code{\link{cppFunction}}
3435
}
3536
\item{rebuild}{
3637
see \code{\link{cppFunction}}
38+
}
39+
\item{cacheDir}{
40+
Directory to use for caching shared libraries. If the underlying code passed to \code{sourceCpp} has not changed since the last invocation then a cached version of the shared library is used. The default value of \code{tempdir()} results in the cache being valid only for the current R session. Pass an alternate directory to preserve the cache across R sessions.
3741
}
3842
\item{showOutput}{
3943
see \code{\link{cppFunction}}

man/sourceCpp.Rd

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ Source C++ Code from a File or String
88
and RCPP_MODULE declarations. A shared library is then built and its exported functions and Rcpp modules are made available in the specified environment.
99
}
1010
\usage{
11-
sourceCpp(file = "", code = NULL, env = globalenv(),
12-
embeddedR = TRUE, rebuild = FALSE,
13-
showOutput = verbose, verbose = getOption("verbose"),
14-
dryRun = FALSE)
11+
sourceCpp(file = "", code = NULL, env = globalenv(), embeddedR = TRUE, rebuild = FALSE,
12+
cacheDir = getOption("rcpp.cache.dir", tempdir()), cleanupCacheDir = FALSE,
13+
showOutput = verbose, verbose = getOption("verbose"), dryRun = FALSE)
1514
}
1615
\arguments{
1716
\item{file}{
@@ -28,6 +27,12 @@ sourceCpp(file = "", code = NULL, env = globalenv(),
2827
}
2928
\item{rebuild}{
3029
Force a rebuild of the shared library.
30+
}
31+
\item{cacheDir}{
32+
Directory to use for caching shared libraries. If the underlying file or code passed to \code{sourceCpp} has not changed since the last invocation then a cached version of the shared library is used. The default value of \code{tempdir()} results in the cache being valid only for the current R session. Pass an alternate directory to preserve the cache across R sessions.
33+
}
34+
\item{cleanupCacheDir}{
35+
Cleanup all files in the cacheDir that were not a result of this compilation. Note that this will cleanup the cache from all other calls to sourceCpp with the same cacheDir. This option should therefore only be specified by callers that provide a unique cacheDir per scope (e.g. chunk labels in a weaved document).
3136
}
3237
\item{showOutput}{
3338
\code{TRUE} to print \code{R CMD SHLIB} output to the console.
@@ -71,7 +76,7 @@ fibonacci(10)
7176
*/
7277
}
7378
74-
Multiple R code chunks can be included in a C++ file. R code is sourced after the C++ compliation is completed so all functions and modules will be available to the R code.
79+
Multiple R code chunks can be included in a C++ file. R code is sourced after the C++ compilation is completed so all functions and modules will be available to the R code.
7580
}
7681
7782
\value{

0 commit comments

Comments
 (0)