Vectorization benchmarks

When to read this

This vignette assumes you’ve already read the Get started vignette and you now have a specific performance question: is it worth vectorizing my R integrand?

cubature exposes two calling conventions for the same underlying C routine:

  • Scalar interface (the default) — your R function receives one point at a time and returns one scalar (or length-fDim vector).
  • Vectorized interface (opt-in, vectorInterface = TRUE for hcubature/pcubature or nVec > 1 for Cuba methods) — your R function receives a matrix of points and returns a matrix of values, amortizing R’s per-call overhead over many integrand evaluations at once.

The vectorized interface almost always wins for non-trivial problems, sometimes by a factor of 10–50×, because the dominant cost is R’s function-call overhead rather than the arithmetic inside the integrand. But the speedup depends on your specific integrand, the method you pick, and the dimension. This vignette runs ten representative integrands through every combination and reports timings, so you can make an informed decision rather than guess.

A timing harness

Our harness will provide timing results for hcubature, pcubature (where appropriate), and Cuba cuhre calls — scalar and vectorized variants of each.

library(bench)
library(cubature)

harness <- function(which = NULL,
                    f, fv, lowerLimit, upperLimit, tol = 1e-3, iterations = 20, ...) {

  fns <- c(hc = "Non-vectorized Hcubature",
           hc.v = "Vectorized Hcubature",
           pc = "Non-vectorized Pcubature",
           pc.v = "Vectorized Pcubature",
           cc = "Non-vectorized cubature::cuhre",
           cc_v = "Vectorized cubature::cuhre")
  cc <- function() cubature::cuhre(f = f,
                                   lowerLimit = lowerLimit, upperLimit = upperLimit,
                                   relTol = tol,
                                   ...)
  cc_v <- function() cubature::cuhre(f = fv,
                                     lowerLimit = lowerLimit, upperLimit = upperLimit,
                                     relTol = tol,
                                     nVec = 1024L,
                                     ...)
  hc <- function() cubature::hcubature(f = f,
                                       lowerLimit = lowerLimit,
                                       upperLimit = upperLimit,
                                       tol = tol,
                                       ...)
  hc.v <- function() cubature::hcubature(f = fv,
                                         lowerLimit = lowerLimit,
                                         upperLimit = upperLimit,
                                         tol = tol,
                                         vectorInterface = TRUE,
                                         ...)
  pc <- function() cubature::pcubature(f = f,
                                       lowerLimit = lowerLimit,
                                       upperLimit = upperLimit,
                                       tol = tol,
                                       ...)
  pc.v <- function() cubature::pcubature(f = fv,
                                         lowerLimit = lowerLimit,
                                         upperLimit = upperLimit,
                                         tol = tol,
                                         vectorInterface = TRUE,
                                         ...)

  ndim <- length(lowerLimit)

  if (is.null(which)) {
    fnIndices <- seq_along(fns)
  } else {
    fnIndices <- na.omit(match(which, names(fns)))
  }
  fnList <- lapply(names(fns)[fnIndices], function(x) call(x))

  argList <- c(fnList, iterations = iterations, check = FALSE)
  result <- do.call(bench::mark, args = argList)
  d <- result[seq_along(fnIndices), 1:9]
  d$expression <- fns[fnIndices]
  d
}

We reel off the timing runs.

Example 1

func <- function(x) sin(x[1]) * cos(x[2]) * exp(x[3])
func.v <- function(x) {
    matrix(apply(x, 2, function(z) sin(z[1]) * cos(z[2]) * exp(z[3])), ncol = ncol(x))
}

d <- harness(f = func, fv = func.v,
             lowerLimit = rep(0, 3),
             upperLimit = rep(1, 3),
             tol = 1e-5,
             iterations = 100)[, 1:9]

knitr::kable(d, digits = 3, row.names = FALSE)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 272.12µs 281.2µs 3505.732 23.9KB 71.546 98 2 28ms
Vectorized Hcubature 394.99µs 413.3µs 2396.508 54.5KB 24.207 99 1 41.3ms
Non-vectorized Pcubature 853.27µs 870.84µs 1139.577 0B 59.978 95 5 83.4ms
Vectorized Pcubature 1.19ms 1.22ms 812.282 58.4KB 42.752 95 5 117ms
Non-vectorized cubature::cuhre 590.58µs 608.69µs 1627.072 0B 33.206 98 2 60.2ms
Vectorized cubature::cuhre 588.51µs 614.27µs 1614.600 39.8KB 32.951 98 2 60.7ms

Multivariate normal

Using cubature, we evaluate \[ \int_R\phi(x)dx \] where \(\phi(x)\) is the three-dimensional multivariate normal density with mean 0, and variance \[ \Sigma = \left(\begin{array}{rrr} 1 &\frac{3}{5} &\frac{1}{3}\\ \frac{3}{5} &1 &\frac{11}{15}\\ \frac{1}{3} &\frac{11}{15} & 1 \end{array} \right) \] and \(R\) is \([-\frac{1}{2}, 1] \times [-\frac{1}{2}, 4] \times [-\frac{1}{2}, 2].\)

We construct a scalar function (my_dmvnorm) and a vector analogue (my_dmvnorm_v). First the functions.

m <- 3
sigma <- diag(3)
sigma[2,1] <- sigma[1, 2] <- 3/5 ; sigma[3,1] <- sigma[1, 3] <- 1/3
sigma[3,2] <- sigma[2, 3] <- 11/15
logdet <- sum(log(eigen(sigma, symmetric = TRUE, only.values = TRUE)$values))
my_dmvnorm <- function (x, mean, sigma, logdet) {
    x <- matrix(x, ncol = length(x))
    distval <- stats::mahalanobis(x, center = mean, cov = sigma)
    exp(-(3 * log(2 * pi) + logdet + distval)/2)
}

my_dmvnorm_v <- function (x, mean, sigma, logdet) {
    distval <- stats::mahalanobis(t(x), center = mean, cov = sigma)
    exp(matrix(-(3 * log(2 * pi) + logdet + distval)/2, ncol = ncol(x)))
}

Now the timing.

d <- harness(f = my_dmvnorm, fv = my_dmvnorm_v,
             lowerLimit = rep(-0.5, 3),
             upperLimit = c(1, 4, 2),
             tol = 1e-5,
             iterations = 10,
             mean = rep(0, m), sigma = sigma, logdet = logdet)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 826.22ms 830.43ms 1.191 154.45KB 13.577 10 114 8.4s
Vectorized Hcubature 2.67ms 2.75ms 360.770 1.89MB 0.000 10 0 27.72ms
Non-vectorized Pcubature 355.42ms 362.06ms 2.722 0B 12.248 10 45 3.67s
Vectorized Pcubature 1.29ms 1.31ms 752.410 810.26KB 0.000 10 0 13.29ms
Non-vectorized cubature::cuhre 339.6ms 342.88ms 2.912 0B 12.520 10 43 3.44s
Vectorized cubature::cuhre 3.26ms 3.34ms 299.228 898.41KB 0.000 10 0 33.42ms

The effect of vectorization is huge. So it makes sense for users to vectorize their integrands as much as possible for efficiency.

Furthermore, for this particular example, we expect mvtnorm::pmvnorm to do pretty well since it is specialized for the multivariate normal. The vectorized versions of hcubature and pcubature seem competitive and in some cases better, as the table below shows.

library(mvtnorm)
g1 <- function() pmvnorm(lower = rep(-0.5, m),
                                  upper = c(1, 4, 2), mean = rep(0, m), corr = sigma,
                                  alg = Miwa(), abseps = 1e-5, releps = 1e-5)
g2 <- function() pmvnorm(lower = rep(-0.5, m),
                         upper = c(1, 4, 2), mean = rep(0, m), corr = sigma,
                         alg = GenzBretz(), abseps = 1e-5, releps = 1e-5)
g3 <- function() pmvnorm(lower = rep(-0.5, m),
                         upper = c(1, 4, 2), mean = rep(0, m), corr = sigma,
                         alg = TVPACK(), abseps = 1e-5, releps = 1e-5)

knitr::kable(bench::mark(g1(), g2(), g3(), iterations = 20, check = FALSE)[, 1:9],
             digits = 3, row.names = FALSE)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
g1() 779µs 1.4ms 722.022 355.33KB 0 20 0 27.7ms
g2() 776µs 1.39ms 782.611 2.49KB 0 20 0 25.6ms
g3() 765µs 1.39ms 844.774 2.49KB 0 20 0 23.7ms

Product of cosines

testFn0 <- function(x) prod(cos(x))
testFn0_v <- function(x) matrix(apply(x, 2, function(z) prod(cos(z))), ncol = ncol(x))

d <- harness(f = testFn0, fv = testFn0_v,
             lowerLimit = rep(0, 2), upperLimit = rep(1, 2), iterations = 1000)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 38µs 40.2µs 24160.654 7.66KB 0.000 1000 0 41.4ms
Vectorized Hcubature 66.2µs 69.4µs 13834.140 1.16KB 13.848 999 1 72.2ms
Non-vectorized Pcubature 51.1µs 55.1µs 17734.548 0B 17.752 999 1 56.3ms
Vectorized Pcubature 117.8µs 124.1µs 7838.017 18.68KB 15.707 998 2 127.3ms
Non-vectorized cubature::cuhre 341µs 351.3µs 2817.869 0B 19.864 993 7 352.4ms
Vectorized cubature::cuhre 363.3µs 379.1µs 2607.881 16.38KB 15.742 994 6 381.2ms

Gaussian function

testFn1 <- function(x) {
    val <- sum(((1 - x) / x)^2)
    scale <- prod((2 / sqrt(pi)) / x^2)
    exp(-val) * scale
}

testFn1_v <- function(x) {
    val <- matrix(apply(x, 2, function(z) sum(((1 - z) / z)^2)), ncol(x))
    scale <- matrix(apply(x, 2, function(z) prod((2 / sqrt(pi)) / z^2)), ncol(x))
    exp(-val) * scale
}

d <- harness(f = testFn1, fv = testFn1_v,
             lowerLimit = rep(0, 3), upperLimit = rep(1, 3), iterations = 10)

knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 2.93ms 2.97ms 336.619 65.2KB 37.402 9 1 26.74ms
Vectorized Hcubature 5.04ms 5.09ms 196.314 290.5KB 0.000 10 0 50.94ms
Non-vectorized Pcubature 80.65µs 84.67µs 11151.032 0B 0.000 10 0 896.78µs
Vectorized Pcubature 162.24µs 167.13µs 5718.918 4.1KB 0.000 10 0 1.75ms
Non-vectorized cubature::cuhre 13.58ms 13.79ms 72.488 0B 18.122 8 2 110.36ms
Vectorized cubature::cuhre 20.26ms 20.49ms 48.701 971.5KB 20.872 7 3 143.73ms

Discontinuous function

testFn2 <- function(x) {
    radius <- 0.50124145262344534123412
    ifelse(sum(x * x) < radius * radius, 1, 0)
}

testFn2_v <- function(x) {
    radius <- 0.50124145262344534123412
    matrix(apply(x, 2, function(z) ifelse(sum(z * z) < radius * radius, 1, 0)), ncol = ncol(x))
}

d <- harness(which = c("hc", "hc.v", "cc", "cc_v"),
             f = testFn2, fv = testFn2_v,
             lowerLimit = rep(0, 2), upperLimit = rep(1, 2), iterations = 10)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 42ms 42.3ms 22.662 17.7KB 24.928 10 11 441.27ms
Vectorized Hcubature 47.3ms 47.4ms 20.898 1011.8KB 22.988 10 11 478.5ms
Non-vectorized cubature::cuhre 800.5ms 806.1ms 1.230 0B 21.531 10 175 8.13s
Vectorized cubature::cuhre 871.1ms 875.7ms 1.136 21.2MB 17.033 10 150 8.81s

A simple polynomial (product of coordinates)

testFn3 <- function(x) prod(2 * x)
testFn3_v <- function(x) matrix(apply(x, 2, function(z) prod(2 * z)), ncol = ncol(x))

d <- harness(f = testFn3, fv = testFn3_v,
             lowerLimit = rep(0, 3), upperLimit = rep(1, 3), iterations = 50)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 59µs 61.4µs 15720.250 6.75KB 0.00 50 0 3.18ms
Vectorized Hcubature 93.2µs 96.3µs 9984.712 2.91KB 0.00 50 0 5.01ms
Non-vectorized Pcubature 54.3µs 56.1µs 17041.410 0B 0.00 50 0 2.93ms
Vectorized Pcubature 85.8µs 90µs 10747.824 19.12KB 0.00 50 0 4.65ms
Non-vectorized cubature::cuhre 656.6µs 682.6µs 1458.235 0B 29.76 49 1 33.6ms
Vectorized cubature::cuhre 651µs 681.1µs 1467.047 39.84KB 0.00 50 0 34.08ms

Gaussian centered at \(\frac{1}{2}\)

testFn4 <- function(x) {
    a <- 0.1
    s <- sum((x - 0.5)^2)
    ((2 / sqrt(pi)) / (2. * a))^length(x) * exp (-s / (a * a))
}

testFn4_v <- function(x) {
    a <- 0.1
    r <- apply(x, 2, function(z) {
        s <- sum((z - 0.5)^2)
        ((2 / sqrt(pi)) / (2. * a))^length(z) * exp (-s / (a * a))
    })
    matrix(r, ncol = ncol(x))
}

d <- harness(f = testFn4, fv = testFn4_v,
             lowerLimit = rep(0, 2), upperLimit = rep(1, 2), iterations = 20)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 1.39ms 1.4ms 707.918 85.2KB 37.259 19 1 26.8ms
Vectorized Hcubature 1.81ms 1.84ms 543.983 147.2KB 28.631 19 1 34.9ms
Non-vectorized Pcubature 2.07ms 2.11ms 473.402 0B 0.000 20 0 42.2ms
Vectorized Pcubature 2.62ms 2.64ms 378.338 68.9KB 19.913 19 1 50.2ms
Non-vectorized cubature::cuhre 3.35ms 3.39ms 292.929 0B 32.548 18 2 61.4ms
Vectorized cubature::cuhre 3.78ms 3.88ms 256.636 125.6KB 13.507 19 1 74ms

Double Gaussian

testFn5 <- function(x) {
    a <- 0.1
    s1 <- sum((x - 1 / 3)^2)
    s2 <- sum((x - 2 / 3)^2)
    0.5 * ((2 / sqrt(pi)) / (2. * a))^length(x) * (exp(-s1 / (a * a)) + exp(-s2 / (a * a)))
}
testFn5_v <- function(x) {
    a <- 0.1
    r <- apply(x, 2, function(z) {
        s1 <- sum((z - 1 / 3)^2)
        s2 <- sum((z - 2 / 3)^2)
        0.5 * ((2 / sqrt(pi)) / (2. * a))^length(z) * (exp(-s1 / (a * a)) + exp(-s2 / (a * a)))
    })
    matrix(r, ncol = ncol(x))
}

d <- harness(f = testFn5, fv = testFn5_v,
             lowerLimit = rep(0, 2), upperLimit = rep(1, 2), iterations = 20)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 3.65ms 3.7ms 268.518 133KB 14.133 19 1 70.8ms
Vectorized Hcubature 4.63ms 4.67ms 213.404 249KB 23.712 18 2 84.3ms
Non-vectorized Pcubature 2.54ms 2.56ms 390.574 0B 20.557 19 1 48.6ms
Vectorized Pcubature 3.27ms 3.33ms 300.653 69KB 15.824 19 1 63.2ms
Non-vectorized cubature::cuhre 7.08ms 7.14ms 139.891 0B 15.543 18 2 128.7ms
Vectorized cubature::cuhre 8.26ms 8.32ms 119.665 224KB 21.117 17 3 142.1ms

Tsuda’s example

testFn6 <- function(x) {
    a <- (1 + sqrt(10.0)) / 9.0
    prod( a / (a + 1) * ((a + 1) / (a + x))^2)
}

testFn6_v <- function(x) {
    a <- (1 + sqrt(10.0)) / 9.0
    r <- apply(x, 2, function(z) prod( a / (a + 1) * ((a + 1) / (a + z))^2))
    matrix(r, ncol = ncol(x))
}

d <- harness(f = testFn6, fv = testFn6_v,
             lowerLimit = rep(0, 3), upperLimit = rep(1, 3), iterations = 20)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 1.62ms 1.63ms 600.156 64.6KB 31.587 19 1 31.7ms
Vectorized Hcubature 2.15ms 2.18ms 457.613 156KB 24.085 19 1 41.5ms
Non-vectorized Pcubature 8.34ms 8.47ms 118.031 0B 20.829 17 3 144ms
Vectorized Pcubature 10.34ms 10.42ms 95.602 386.2KB 16.871 17 3 177.8ms
Non-vectorized cubature::cuhre 4.42ms 4.48ms 222.271 0B 24.697 18 2 81ms
Vectorized cubature::cuhre 4.76ms 4.83ms 206.602 225.8KB 10.874 19 1 92ms

Morokoff & Caflisch example

testFn7 <- function(x) {
    n <- length(x)
    p <- 1/n
    (1 + p)^n * prod(x^p)
}
testFn7_v <- function(x) {
    matrix(apply(x, 2, function(z) {
        n <- length(z)
        p <- 1/n
        (1 + p)^n * prod(z^p)
    }), ncol = ncol(x))
}

d <- harness(f = testFn7, fv = testFn7_v,
             lowerLimit = rep(0, 3), upperLimit = rep(1, 3), iterations = 20)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 3.35ms 3.39ms 293.445 32.89KB 15.444 19 1 64.7ms
Vectorized Hcubature 4.01ms 4.06ms 245.172 205.61KB 27.241 18 2 73.4ms
Non-vectorized Pcubature 8.33ms 8.43ms 118.584 0B 20.927 17 3 143.4ms
Vectorized Pcubature 9.54ms 9.65ms 103.585 386.24KB 25.896 16 4 154.5ms
Non-vectorized cubature::cuhre 40.86ms 41.11ms 24.304 0B 72.911 5 15 205.7ms
Vectorized cubature::cuhre 40.84ms 41.03ms 24.349 2.04MB 56.815 6 14 246.4ms

Wang-Landau sampling 1d, 2d examples

I.1d <- function(x) {
    sin(4 * x) *
        x * ((x * ( x * (x * x - 4) + 1) - 1))
}
I.1d_v <- function(x) {
    matrix(apply(x, 2, function(z)
        sin(4 * z) *
        z * ((z * ( z * (z * z - 4) + 1) - 1))),
        ncol = ncol(x))
}
d <- harness(f = I.1d, fv = I.1d_v,
             lowerLimit = -2, upperLimit = 2, iterations = 100)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 133.8µs 145.6µs 6684.214 53.7KB 67.517 99 1 14.81ms
Vectorized Hcubature 217.7µs 225.5µs 4311.464 68.5KB 0.000 100 0 23.19ms
Non-vectorized Pcubature 56.2µs 58.8µs 16373.566 0B 0.000 100 0 6.11ms
Vectorized Pcubature 160.6µs 164.9µs 5866.920 0B 0.000 100 0 17.05ms
Non-vectorized cubature::cuhre 230.1µs 239.6µs 4096.623 0B 41.380 99 1 24.17ms
Vectorized cubature::cuhre 510.3µs 530.1µs 1887.287 0B 0.000 100 0 52.99ms
I.2d <- function(x) {
    x1 <- x[1]; x2 <- x[2]
    sin(4 * x1 + 1) * cos(4 * x2) * x1 * (x1 * (x1 * x1)^2 - x2 * (x2 * x2 - x1) +2)
}
I.2d_v <- function(x) {
    matrix(apply(x, 2,
                 function(z) {
                     x1 <- z[1]; x2 <- z[2]
                     sin(4 * x1 + 1) * cos(4 * x2) * x1 * (x1 * (x1 * x1)^2 - x2 * (x2 * x2 - x1) +2)
                 }),
           ncol = ncol(x))
}
d <- harness(f = I.2d, fv = I.2d_v,
             lowerLimit = rep(-1, 2), upperLimit = rep(1, 2), iterations = 100)
knitr::kable(d, digits = 3)
expression min median itr/sec mem_alloc gc/sec n_itr n_gc total_time
Non-vectorized Hcubature 4.66ms 4.73ms 211.131 78.4KB 20.881 91 9 431ms
Vectorized Hcubature 5.65ms 5.72ms 174.587 304.7KB 17.267 91 9 521.2ms
Non-vectorized Pcubature 424.52µs 440.77µs 2255.637 0B 0.000 100 0 44.3ms
Vectorized Pcubature 622.34µs 654.02µs 1518.458 18.3KB 15.338 99 1 65.2ms
Non-vectorized cubature::cuhre 1.29ms 1.32ms 750.316 0B 15.313 98 2 130.6ms
Vectorized cubature::cuhre 1.39ms 1.42ms 699.561 60.1KB 14.277 98 2 140.1ms

Summary

The recommendations are:

  1. Vectorize your integrand. The time spent in so doing pays back enormously on all but the most trivial problems. This is easy to do and the examples above show how.

  2. Vectorized hcubature is a good default starting point. It handles the widest range of problems well.

  3. For smooth integrands in low dimensions (\(\leq 3\)), try pcubature. It can be substantially faster than hcubature when its assumptions hold. Experiment before committing in a production setting, and be aware of the sampling-density cliff documented in the Get Started vignette — when in doubt, cross-check with cuhre.

  4. For non-smooth or localized integrands, prefer cubintegrate(method = "cuhre") or at least hcubature(..., robust = TRUE). See the Robustness section of the Get Started vignette.

Session info

sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 26.04.1 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] mvtnorm_1.4-2  bench_1.1.4    cubature_2.2.0
## 
## loaded via a namespace (and not attached):
##  [1] vctrs_0.7.3      cli_3.6.6        knitr_1.52       rlang_1.3.0     
##  [5] xfun_0.61        otel_0.2.0       jsonlite_2.0.0   glue_1.8.1      
##  [9] buildtools_1.0.0 htmltools_0.5.9  maketools_1.3.2  sys_3.4.3       
## [13] sass_0.4.10      rmarkdown_2.32   tibble_3.3.1     evaluate_1.0.5  
## [17] jquerylib_0.1.4  fastmap_1.2.0    yaml_2.3.12      profmem_0.7.0   
## [21] lifecycle_1.0.5  compiler_4.6.1   pkgconfig_2.0.3  Rcpp_1.1.2      
## [25] digest_0.6.39    R6_2.6.1         pillar_1.11.1    magrittr_2.0.5  
## [29] bslib_0.12.0     tools_4.6.1      cachem_1.1.0