Skip to content

Commit ce6b51f

Browse files
committed
rewrite the fenwick stuff in cpp11 and rebase
1 parent 434224f commit ce6b51f

12 files changed

Lines changed: 567 additions & 303 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ Remotes:
4040
jasp-stats/jaspTTests,
4141
jasp-stats/jaspTools
4242
LinkingTo:
43-
Rcpp
43+
cpp11

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
useDynLib(jaspRegression, .registration = TRUE)
12
import(jaspBase)
23
export(Correlation)
34
export(RegressionLinear)

R/concordance.R

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
concordance <- function(x, y) {
2-
3-
n <- length(x)
4-
stopifnot(length(y) == n)
5-
if (n == 0L) return(integer(0L))
6-
if (n == 1L) return(0L)
7-
if (n <= 130) concordance_naive(x, y)
8-
concordance_fenwick(x, y)
9-
2+
stopifnot(length(x) == length(y))
3+
concordance_fenwick_cpp(as.double(x), as.double(y))
104
}
115

126
concordance_naive <- function(x, y) {

R/cpp11.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Generated by cpp11: do not edit by hand
2+
3+
concordance_naive_cpp <- function(x, y) {
4+
.Call(`_jaspRegression_concordance_naive_cpp`, x, y)
5+
}
6+
7+
concordance_fenwick_cpp <- function(x, y) {
8+
.Call(`_jaspRegression_concordance_fenwick_cpp`, x, y)
9+
}

benchmarks/concordance.html

Lines changed: 242 additions & 211 deletions
Large diffs are not rendered by default.

benchmarks/concordance.qmd

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ execute:
99
---
1010

1111
```{r, echo=TRUE, include=FALSE}
12-
library(Rcpp)
12+
renv::activate(".")
13+
library(jaspRegression)
1314
# for benchmarking
1415
library(bench)
1516
# for manipulating bench output
@@ -108,10 +109,14 @@ This is the same trick used in fast Kendall’s tau algorithms, but adapted to r
108109

109110
## 4. Implementation of the optimized solution
110111

112+
The Fenwick tree algorithm is implemented in C++ using `cpp11` in `src/concordance.cpp` and registered as `concordance_fenwick_cpp`.
113+
For reference, here is the equivalent pure-R implementation:
114+
111115
```{r}
112116
#| label: fenwick
113-
# in-place Fenwick (handles ties in x and y; minimal allocations)
114-
# the code is a bit messy because R does not allow mutation
117+
concordance_fenwick_cpp <- jaspRegression:::concordance_fenwick_cpp
118+
119+
# Pure-R reference implementation (kept for documentation)
115120
concordance_fenwick_inplace <- function(x, y) {
116121
n <- length(x)
117122
stopifnot(length(y) == n)
@@ -266,26 +271,24 @@ We compare the naive and optimized versions using the `bench` package.
266271
#| label: benchmark
267272
#| cache: false
268273
274+
concordance_naive_cpp <- function(x, y) jaspRegression:::concordance_naive_cpp(as.double(x), as.double(y))
275+
concordance_fenwick_cpp <- function(x, y) jaspRegression:::concordance_fenwick_cpp(as.double(x), as.double(y))
276+
269277
# results with ties
270278
x <- sample(-5:5, 100, TRUE)
271279
y <- sample(-5:5, 100, TRUE)
272280
naive = concordance_naive(x, y)
273-
naive_cpp = concordanceVector_cpp(x, y)
274-
fenwick2 = concordance_fenwick_inplace(x, y)
275-
all(naive == fenwick2) && all(naive_cpp == fenwick2)
281+
naive_cpp = concordance_naive_cpp(x, y)
282+
fenwick = concordance_fenwick_cpp(x, y)
283+
all(naive == fenwick) && all(naive_cpp == fenwick)
276284
277285
# results without ties
278286
x <- rnorm(10)
279287
y <- rnorm(10)
280288
naive = concordance_naive(x, y)
281-
naive_cpp = concordanceVector_cpp(x, y)
282-
fenwick2 = concordance_fenwick_inplace(x, y)
283-
all(naive == fenwick2) && all(naive_cpp == fenwick2)
284-
285-
# profvis::profvis({
286-
# for (i in 1:10000)
287-
# concordance_fenwick_inplace(x, y)
288-
# })
289+
naive_cpp = concordance_naive_cpp(x, y)
290+
fenwick = concordance_fenwick_cpp(x, y)
291+
all(naive == fenwick) && all(naive_cpp == fenwick)
289292
290293
orders <- 3
291294
ns <- c(sapply(seq_len(orders), \(o) {
@@ -300,8 +303,8 @@ res <- suppressWarnings(bench::press(
300303
y <- rnorm(n)
301304
bench::mark(
302305
naive = concordance_naive(x, y),
303-
naive_cpp = concordanceVector_cpp(x, y),
304-
fenwick = concordance_fenwick_inplace(x, y),
306+
naive_cpp = concordance_naive_cpp(x, y),
307+
fenwick = concordance_fenwick_cpp(x, y),
305308
check = TRUE, # ensure results are checked for equality
306309
iterations = 3
307310
)
@@ -334,7 +337,7 @@ lm_res <- res |>
334337
time_slope = unname(sapply(time_fit, \(x) coef(x)["log10(n)"])),
335338
mem_intercept = unname(sapply(mem_fit, \(x) coef(x)["(Intercept)"])),
336339
mem_slope = unname(sapply(mem_fit, \(x) coef(x)["log10(n)"])),
337-
# Remove the temporary columns if you don't need them
340+
# Remove the temporary columns
338341
.keep = "unused"
339342
)
340343
@@ -396,5 +399,6 @@ plot(plt_time + plt_memory + plot_layout(guides = "collect"))
396399

397400
## 7. Conclusions
398401

399-
* On my machine, the Fenwick approach outperforms the naive approach around $n=$ `r intersection_n_time_rounded[1]` and the naive C++ approach around $n=$ `r intersection_n_time_rounded[2]`. Theoretically, the naive approach is $\mathcal{O}(n^2)$ in time, while the Fenwick approaches is $\mathcal{O}(n \log n)$.
400-
* The naive approach always uses more memory than the fenwick approach, which uses more memory than the C++ approach. Theoretically, the naive approach is $\mathcal{O}(n^2)$ in memory, while the Fenwick and C++ approaches are $\mathcal{O}(n)$.
402+
* On my machine, the Fenwick C++ approach outperforms the naive R approach around $n=$ `r intersection_n_time_rounded[1]` and the naive C++ approach around $n=$ `r intersection_n_time_rounded[2]`. Theoretically, the naive approaches are $\mathcal{O}(n^2)$ in time, while the Fenwick approach is $\mathcal{O}(n \log n)$.
403+
* The naive R approach always uses more memory than the Fenwick and naive C++ approaches. Theoretically, the naive R approach is $\mathcal{O}(n^2)$ in memory, while the Fenwick and naive C++ approaches are $\mathcal{O}(n)$.
404+
* All C++ implementations use `cpp11` (no Rcpp dependency) for faster compilation.

renv.lock

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7881,57 +7881,11 @@
78817881
},
78827882
"renv": {
78837883
"Package": "renv",
7884-
"Version": "1.1.5",
7885-
"Source": "Repository",
7886-
"Type": "Package",
7887-
"Title": "Project Environments",
7888-
"Authors@R": "c( person(\"Kevin\", \"Ushey\", role = c(\"aut\", \"cre\"), email = \"kevin@rstudio.com\", comment = c(ORCID = \"0000-0003-2880-7407\")), person(\"Hadley\", \"Wickham\", role = c(\"aut\"), email = \"hadley@rstudio.com\", comment = c(ORCID = \"0000-0003-4757-117X\")), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")) )",
7889-
"Description": "A dependency management toolkit for R. Using 'renv', you can create and manage project-local R libraries, save the state of these libraries to a 'lockfile', and later restore your library as required. Together, these tools can help make your projects more isolated, portable, and reproducible.",
7890-
"License": "MIT + file LICENSE",
7891-
"URL": "https://rstudio.github.io/renv/, https://github.com/rstudio/renv",
7892-
"BugReports": "https://github.com/rstudio/renv/issues",
7893-
"Imports": [
7894-
"utils"
7895-
],
7896-
"Suggests": [
7897-
"BiocManager",
7898-
"cli",
7899-
"compiler",
7900-
"covr",
7901-
"cpp11",
7902-
"devtools",
7903-
"generics",
7904-
"gitcreds",
7905-
"jsonlite",
7906-
"jsonvalidate",
7907-
"knitr",
7908-
"miniUI",
7909-
"modules",
7910-
"packrat",
7911-
"pak",
7912-
"R6",
7913-
"remotes",
7914-
"reticulate",
7915-
"rmarkdown",
7916-
"rstudioapi",
7917-
"shiny",
7918-
"testthat",
7919-
"uuid",
7920-
"waldo",
7921-
"yaml",
7922-
"webfakes"
7923-
],
7924-
"Encoding": "UTF-8",
7925-
"RoxygenNote": "7.3.2",
7926-
"VignetteBuilder": "knitr",
7927-
"Config/Needs/website": "tidyverse/tidytemplate",
7928-
"Config/testthat/edition": "3",
7929-
"Config/testthat/parallel": "true",
7930-
"Config/testthat/start-first": "bioconductor,python,install,restore,snapshot,retrieve,remotes",
7884+
"Version": "1.2.1",
7885+
"OS_type": null,
79317886
"NeedsCompilation": "no",
7932-
"Author": "Kevin Ushey [aut, cre] (ORCID: <https://orcid.org/0000-0003-2880-7407>), Hadley Wickham [aut] (ORCID: <https://orcid.org/0000-0003-4757-117X>), Posit Software, PBC [cph, fnd]",
7933-
"Maintainer": "Kevin Ushey <kevin@rstudio.com>",
7934-
"Repository": "CRAN"
7887+
"Repository": "CRAN",
7888+
"Source": "Repository"
79357889
},
79367890
"reshape2": {
79377891
"Package": "reshape2",

renv/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
library/
2+
local/
3+
cellar/
4+
lock/
5+
python/
6+
sandbox/
7+
staging/

renv/activate.R

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
local({
33

44
# the requested version of renv
5-
version <- "1.1.5"
5+
version <- "1.2.1"
6+
attr(version, "md5") <- "4c1fc7ec8f70bf77013fa09fa31b3a4f"
67
attr(version, "sha") <- NULL
78

89
# the project directory
@@ -168,6 +169,16 @@ local({
168169
if (quiet)
169170
return(invisible())
170171

172+
# also check for config environment variables that should suppress messages
173+
# https://github.com/rstudio/renv/issues/2214
174+
enabled <- Sys.getenv("RENV_CONFIG_STARTUP_QUIET", unset = NA)
175+
if (!is.na(enabled) && tolower(enabled) %in% c("true", "1"))
176+
return(invisible())
177+
178+
enabled <- Sys.getenv("RENV_CONFIG_SYNCHRONIZED_CHECK", unset = NA)
179+
if (!is.na(enabled) && tolower(enabled) %in% c("false", "0"))
180+
return(invisible())
181+
171182
msg <- sprintf(fmt, ...)
172183
cat(msg, file = stdout(), sep = if (appendLF) "\n" else "")
173184

@@ -215,6 +226,20 @@ local({
215226
section <- header(sprintf("Bootstrapping renv %s", friendly))
216227
catf(section)
217228

229+
# ensure the target library path exists; required for file.copy(..., recursive = TRUE)
230+
dir.create(library, showWarnings = FALSE, recursive = TRUE)
231+
232+
# try to install renv from cache
233+
md5 <- attr(version, "md5", exact = TRUE)
234+
if (length(md5)) {
235+
pkgpath <- renv_bootstrap_find(version)
236+
if (length(pkgpath) && file.exists(pkgpath)) {
237+
ok <- file.copy(pkgpath, library, recursive = TRUE)
238+
if (isTRUE(ok))
239+
return(invisible())
240+
}
241+
}
242+
218243
# attempt to download renv
219244
catf("- Downloading renv ... ", appendLF = FALSE)
220245
withCallingHandlers(
@@ -240,7 +265,6 @@ local({
240265

241266
# add empty line to break up bootstrapping from normal output
242267
catf("")
243-
244268
return(invisible())
245269
}
246270

@@ -257,12 +281,20 @@ local({
257281
repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA)
258282
if (!is.na(repos)) {
259283

260-
# check for RSPM; if set, use a fallback repository for renv
261-
rspm <- Sys.getenv("RSPM", unset = NA)
262-
if (identical(rspm, repos))
263-
repos <- c(RSPM = rspm, CRAN = cran)
284+
# split on ';' if present
285+
parts <- strsplit(repos, ";", fixed = TRUE)[[1L]]
264286

265-
return(repos)
287+
# split into named repositories if present
288+
idx <- regexpr("=", parts, fixed = TRUE)
289+
keys <- substring(parts, 1L, idx - 1L)
290+
vals <- substring(parts, idx + 1L)
291+
names(vals) <- keys
292+
293+
# if we have a single unnamed repository, call it CRAN
294+
if (length(vals) == 1L && identical(keys, ""))
295+
names(vals) <- "CRAN"
296+
297+
return(vals)
266298

267299
}
268300

@@ -511,6 +543,51 @@ local({
511543

512544
}
513545

546+
renv_bootstrap_find <- function(version) {
547+
548+
path <- renv_bootstrap_find_cache(version)
549+
if (length(path) && file.exists(path)) {
550+
catf("- Using renv %s from global package cache", version)
551+
return(path)
552+
}
553+
554+
}
555+
556+
renv_bootstrap_find_cache <- function(version) {
557+
558+
md5 <- attr(version, "md5", exact = TRUE)
559+
if (is.null(md5))
560+
return()
561+
562+
# infer path to renv cache
563+
cache <- Sys.getenv("RENV_PATHS_CACHE", unset = "")
564+
if (!nzchar(cache)) {
565+
root <- Sys.getenv("RENV_PATHS_ROOT", unset = NA)
566+
if (!is.na(root))
567+
cache <- file.path(root, "cache")
568+
}
569+
570+
if (!nzchar(cache)) {
571+
tools <- asNamespace("tools")
572+
if (is.function(tools$R_user_dir)) {
573+
root <- tools$R_user_dir("renv", "cache")
574+
cache <- file.path(root, "cache")
575+
}
576+
}
577+
578+
# start completing path to cache
579+
file.path(
580+
cache,
581+
renv_bootstrap_cache_version(),
582+
renv_bootstrap_platform_prefix(),
583+
"renv",
584+
version,
585+
md5,
586+
"renv"
587+
)
588+
589+
}
590+
514591
renv_bootstrap_download_tarball <- function(version) {
515592

516593
# if the user has provided the path to a tarball via
@@ -979,7 +1056,7 @@ local({
9791056

9801057
renv_bootstrap_validate_version_release <- function(version, description) {
9811058
expected <- description[["Version"]]
982-
is.character(expected) && identical(expected, version)
1059+
is.character(expected) && identical(c(expected), c(version))
9831060
}
9841061

9851062
renv_bootstrap_hash_text <- function(text) {
@@ -1158,6 +1235,21 @@ local({
11581235
}
11591236

11601237
renv_bootstrap_run <- function(project, libpath, version) {
1238+
tryCatch(
1239+
renv_bootstrap_run_impl(project, libpath, version),
1240+
error = function(e) {
1241+
msg <- paste(
1242+
"failed to bootstrap renv: the project will not be loaded.",
1243+
paste("Reason:", conditionMessage(e)),
1244+
"Use `renv::activate()` to re-initialize the project.",
1245+
sep = "\n"
1246+
)
1247+
warning(msg, call. = FALSE)
1248+
}
1249+
)
1250+
}
1251+
1252+
renv_bootstrap_run_impl <- function(project, libpath, version) {
11611253

11621254
# perform bootstrap
11631255
bootstrap(version, libpath)
@@ -1181,6 +1273,18 @@ local({
11811273

11821274
}
11831275

1276+
renv_bootstrap_cache_version <- function() {
1277+
# NOTE: users should normally not override the cache version;
1278+
# this is provided just to make testing easier
1279+
Sys.getenv("RENV_CACHE_VERSION", unset = "v5")
1280+
}
1281+
1282+
renv_bootstrap_cache_version_previous <- function() {
1283+
version <- renv_bootstrap_cache_version()
1284+
number <- as.integer(substring(version, 2L))
1285+
paste("v", number - 1L, sep = "")
1286+
}
1287+
11841288
renv_json_read <- function(file = NULL, text = NULL) {
11851289

11861290
jlerr <- NULL

0 commit comments

Comments
 (0)