This document serves as an overview for measuring the performance of
RcppAlgos against other tools for generating combinations,
permutations, and partitions. This stackoverflow post: How to generate
permutations or combinations of object in R? has some benchmarks.
You will note that the examples in that post are relatively small. The
benchmarks below will focus on larger examples where performance really
matters and for this reason we only consider the packages arrangements,
partitions,
and RcppAlgos.
Setup Information
For the benchmarks below, we used a
2025 Macbook Air Apple M4 24 GB machine.
library(RcppAlgos)
library(partitions)
library(arrangements)
#>
#> Attaching package: 'arrangements'
#> The following object is masked from 'package:partitions':
#>
#> compositions
library(microbenchmark)
options(digits = 4)
options(width = 90)
pertinent_output <- capture.output(sessionInfo())
cat(paste(pertinent_output[1:3], collapse = "\n"))
#> R version 4.5.2 (2025-10-31)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sequoia 15.7.4
pkgs <- c("RcppAlgos", "arrangements", "partitions", "microbenchmark")
sapply(pkgs, packageVersion, simplify = FALSE)
#> $RcppAlgos
#> [1] '2.10.0'
#>
#> $arrangements
#> [1] '1.1.9'
#>
#> $partitions
#> [1] '1.10.9'
#>
#> $microbenchmark
#> [1] '1.5.0'
numThreads <- min(as.integer(RcppAlgos::stdThreadMax() / 2), 6)
numThreads
#> [1] 5Combinations
Combinations - Distinct
set.seed(13)
v1 <- sort(sample(100, 30))
m <- 21
t1 <- comboGeneral(v1, m, Parallel = T)
t2 <- combinations(v1, m)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 14307150 21
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = comboGeneral(v1, m, nThreads = numThreads),
cbRcppAlgosSer = comboGeneral(v1, m),
cbArrangements = combinations(v1, m),
times = 15, unit = "relative")
#> Warning in microbenchmark(cbRcppAlgosPar = comboGeneral(v1, m, nThreads = numThreads), :
#> less accurate nanosecond times to avoid potential integer overflows
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 15
#> cbRcppAlgosSer 3.360 3.111 3.024 3.055 3.027 2.695 15
#> cbArrangements 3.371 3.150 3.053 3.092 3.062 2.708 15Combinations - Repetition
v2 <- v1[1:10]
m <- 20
t1 <- comboGeneral(v2, m, repetition = TRUE, nThreads = numThreads)
t2 <- combinations(v2, m, replace = TRUE)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 10015005 20
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = comboGeneral(v2, m, TRUE, nThreads = numThreads),
cbRcppAlgosSer = comboGeneral(v2, m, TRUE),
cbArrangements = combinations(v2, m, replace = TRUE),
times = 15, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 15
#> cbRcppAlgosSer 3.119 2.993 2.956 2.986 2.940 2.689 15
#> cbArrangements 2.963 2.895 2.857 2.882 2.829 2.722 15Combinations - Multisets
myFreqs <- c(2, 4, 4, 5, 3, 2, 2, 2, 3, 4, 1, 4, 2, 5)
v3 <- as.integer(c(1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610))
t1 <- comboGeneral(v3, 20, freqs = myFreqs, nThreads = numThreads)
t2 <- combinations(freq = myFreqs, k = 20, x = v3)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 14594082 20
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = comboGeneral(v3, 20, freqs = myFreqs, nThreads = numThreads),
cbRcppAlgosSer = comboGeneral(v3, 20, freqs = myFreqs),
cbArrangements = combinations(freq = myFreqs, k = 20, x = v3),
times = 10, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 10
#> cbRcppAlgosSer 3.293 3.164 3.073 3.096 3.016 2.757 10
#> cbArrangements 6.804 6.579 6.332 6.421 6.241 5.418 10Permutations
Permutations - Distinct
v4 <- as.integer(c(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59))
t1 <- permuteGeneral(v4, 6, nThreads = numThreads)
t2 <- permutations(v4, 6)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 8910720 6
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = permuteGeneral(v4, 6, nThreads = numThreads),
cbRcppAlgosSer = permuteGeneral(v4, 6),
cbArrangements = permutations(v4, 6),
times = 15, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 15
#> cbRcppAlgosSer 1.494 1.446 1.352 1.421 1.364 1.283 15
#> cbArrangements 2.177 2.110 2.032 2.081 2.135 1.793 15
## Indexing permutation example with the partitions package
t1 <- permuteGeneral(11, nThreads = 4)
t2 <- permutations(11)
t3 <- perms(11)
dim(t1)
#> [1] 39916800 11
stopifnot(identical(t1, t2), identical(t1, t(as.matrix(t3))))
rm(t1, t2, t3)
invisible(gc())
microbenchmark(cbRcppAlgosPar = permuteGeneral(11, nThreads = 4),
cbRcppAlgosSer = permuteGeneral(11),
cbArrangements = permutations(11),
cbPartitions = perms(11),
times = 5, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 5
#> cbRcppAlgosSer 2.223 2.538 2.263 2.528 1.879 2.312 5
#> cbArrangements 3.537 3.568 3.267 3.432 2.610 3.430 5
#> cbPartitions 9.250 9.446 7.978 9.493 6.656 6.588 5Permutations - Repetition
v5 <- v3[1:5]
t1 <- permuteGeneral(v5, 10, repetition = TRUE, nThreads = numThreads)
t2 <- permutations(v5, 10, replace = TRUE)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 9765625 10
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = permuteGeneral(v5, 10, TRUE, nThreads = numThreads),
cbRcppAlgosSer = permuteGeneral(v5, 10, TRUE),
cbArrangements = permutations(x = v5, k = 10, replace = TRUE),
times = 10, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.0000 10
#> cbRcppAlgosSer 2.423 2.355 1.997 2.327 2.159 0.9621 10
#> cbArrangements 2.822 2.768 2.494 2.712 2.521 1.8244 10Permutations - Multisets
v6 <- sort(runif(12))
t1 <- permuteGeneral(v6, 7, freqs = rep(1:3, 4), nThreads = numThreads)
t2 <- permutations(freq = rep(1:3, 4), k = 7, x = v6)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 19520760 7
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = permuteGeneral(v6, 7, freqs = rep(1:3, 4), nThreads = numThreads),
cbRcppAlgosSer = permuteGeneral(v6, 7, freqs = rep(1:3, 4)),
cbArrangements = permutations(freq = rep(1:3, 4), k = 7, x = v6),
times = 10, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 10
#> cbRcppAlgosSer 3.473 3.511 3.153 3.367 2.552 2.731 10
#> cbArrangements 3.937 3.985 3.658 3.874 3.138 3.117 10Partitions
Partitions - Distinct
All Distinct Partitions
t1 <- comboGeneral(0:140, freqs=c(140, rep(1, 140)),
constraintFun = "sum", comparisonFun = "==",
limitConstraints = 140)
t2 <- partitions(140, distinct = TRUE)
t3 <- diffparts(140)
# Each package has different output formats... we only examine dimensions
# and that each result is a partition of 140
stopifnot(identical(dim(t1), dim(t2)), identical(dim(t1), dim(t(t3))),
all(rowSums(t1) == 140), all(rowSums(t2) == 140),
all(colSums(t3) == 140))
dim(t1)
#> [1] 9617150 16
rm(t1, t2, t3)
invisible(gc())
microbenchmark(cbRcppAlgosPar = partitionsGeneral(0:140, freqs=c(140, rep(1, 140)), nThreads = numThreads),
cbRcppAlgosSer = partitionsGeneral(0:140, freqs=c(140, rep(1, 140))),
cbArrangements = partitions(140, distinct = TRUE),
cbPartitions = diffparts(140),
times = 10, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 10
#> cbRcppAlgosSer 3.252 3.133 2.911 2.982 2.739 2.709 10
#> cbArrangements 2.492 2.431 2.256 2.292 2.168 2.146 10
#> cbPartitions 17.359 17.199 15.909 17.115 14.684 13.572 10Restricted Distinct Partitions
t1 <- comboGeneral(160, 10,
constraintFun = "sum", comparisonFun = "==",
limitConstraints = 160)
t2 <- partitions(160, 10, distinct = TRUE)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 8942920 10
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = partitionsGeneral(160, 10, nThreads = numThreads),
cbRcppAlgosSer = partitionsGeneral(160, 10),
cbArrangements = partitions(160, 10, distinct = TRUE),
times = 10, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 10
#> cbRcppAlgosSer 3.153 3.082 2.713 3.017 2.973 1.902 10
#> cbArrangements 4.490 4.400 3.700 4.303 4.192 2.230 10Partitions - Repetition
All Partitions
t1 <- comboGeneral(0:65, repetition = TRUE, constraintFun = "sum",
comparisonFun = "==", limitConstraints = 65)
t2 <- partitions(65)
t3 <- parts(65)
# Each package has different output formats... we only examine dimensions
# and that each result is a partition of 65
stopifnot(identical(dim(t1), dim(t2)), identical(dim(t1), dim(t(t3))),
all(rowSums(t1) == 65), all(rowSums(t2) == 65),
all(colSums(t3) == 65))
dim(t1)
#> [1] 2012558 65
rm(t1, t2, t3)
invisible(gc())
microbenchmark(cbRcppAlgosPar = partitionsGeneral(0:65, repetition = TRUE,
nThreads = numThreads),
cbRcppAlgosSer = partitionsGeneral(0:65, repetition = TRUE),
cbArrangements = partitions(65),
cbPartitions = parts(65),
times = 20, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 20
#> cbRcppAlgosSer 2.793 2.749 2.276 2.415 2.182 1.776 20
#> cbArrangements 2.011 1.961 1.628 1.746 1.644 1.076 20
#> cbPartitions 9.885 9.768 7.711 8.600 7.274 4.866 20Restricted Partitions
t1 <- comboGeneral(100, 15, TRUE, constraintFun = "sum",
comparisonFun = "==", limitConstraints = 100)
t2 <- partitions(100, 15)
stopifnot(identical(t1, t2))
dim(t1)
#> [1] 9921212 15
rm(t1, t2)
# This takes a really long time... not because of restrictedparts,
# but because apply is not that fast. This transformation is
# needed for proper comparisons. As a result, we will compare
# a smaller example
# t3 <- t(apply(as.matrix(restrictedparts(100, 15, include.zero = F)), 2, sort))
t3 <- t(apply(as.matrix(restrictedparts(50, 15, include.zero = F)), 2, sort))
stopifnot(identical(partitions(50, 15), t3))
rm(t3)
invisible(gc())
microbenchmark(cbRcppAlgosPar = partitionsGeneral(100, 15, TRUE,
nThreads = numThreads),
cbRcppAlgosSer = partitionsGeneral(100, 15, TRUE),
cbArrangements = partitions(100, 15),
cbPartitions = restrictedparts(100, 15,
include.zero = FALSE),
times = 10, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.00 1.000 1.000 1.000 1.000 10
#> cbRcppAlgosSer 3.378 3.31 2.974 2.952 2.735 2.638 10
#> cbArrangements 4.484 4.44 4.079 4.091 3.653 4.024 10
#> cbPartitions 15.181 15.09 13.452 13.576 12.017 11.979 10Partitions - Multisets
Currently, RcppAlgos is the only package capable of
efficiently generating partitions of multisets. Therefore, we will only
time RcppAlgos and use this as a reference for future
improvements.
t1 <- comboGeneral(120, 10, freqs = rep(1:8, 15),
constraintFun = "sum", comparisonFun = "==",
limitConstraints = 120)
dim(t1)
#> [1] 7340225 10
stopifnot(all(rowSums(t1) == 120))
microbenchmark(cbRcppAlgos = partitionsGeneral(120, 10, freqs = rep(1:8, 15)),
times = 10)
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> cbRcppAlgos 178 180.3 182.4 181.4 182.9 189.6 10Compositions
Compositions - Repetition
All Compositions (Small case)
t1 <- compositionsGeneral(0:15, repetition = TRUE)
t2 <- arrangements::compositions(15)
t3 <- partitions::compositions(15)
# Each package has different output formats... we only examine dimensions
# and that each result is a partition of 15
stopifnot(identical(dim(t1), dim(t2)), identical(dim(t1), dim(t(t3))),
all(rowSums(t1) == 15), all(rowSums(t2) == 15),
all(colSums(t3) == 15))
dim(t1)
#> [1] 16384 15
rm(t1, t2, t3)
invisible(gc())
microbenchmark(cbRcppAlgosSer = compositionsGeneral(0:15, repetition = TRUE),
cbArrangements = arrangements::compositions(15),
cbPartitions = partitions::compositions(15),
times = 20, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosSer 1.107 1.101 1.098 1.096 1.094 1.053 20
#> cbArrangements 1.000 1.000 1.000 1.000 1.000 1.000 20
#> cbPartitions 110.419 117.834 145.577 145.641 165.113 191.249 20For the next two examples, we will exclude the
partitions package for efficiency reasons.
All Compositions (Larger case)
t1 <- compositionsGeneral(0:23, repetition = TRUE)
t2 <- arrangements::compositions(23)
# Each package has different output formats... we only examine dimensions
# and that each result is a partition of 23
stopifnot(identical(dim(t1), dim(t2)), all(rowSums(t1) == 23),
all(rowSums(t2) == 23))
dim(t1)
#> [1] 4194304 23
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = compositionsGeneral(0:23, repetition = TRUE,
nThreads = numThreads),
cbRcppAlgosSer = compositionsGeneral(0:23, repetition = TRUE),
cbArrangements = arrangements::compositions(23),
times = 20, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 20
#> cbRcppAlgosSer 3.833 3.639 3.562 3.580 3.526 3.051 20
#> cbArrangements 4.138 3.921 3.831 3.853 3.783 3.250 20Compositions of Specific Length
t1 <- compositionsGeneral(30, 10, repetition = TRUE)
t2 <- arrangements::compositions(30, 10)
stopifnot(identical(t1, t2), all(rowSums(t1) == 30))
dim(t1)
#> [1] 10015005 10
rm(t1, t2)
invisible(gc())
microbenchmark(cbRcppAlgosPar = compositionsGeneral(30, 10, repetition = TRUE,
nThreads = numThreads),
cbRcppAlgosSer = compositionsGeneral(30, 10, repetition = TRUE),
cbArrangements = arrangements::compositions(30, 10),
times = 20, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgosPar 1.000 1.000 1.000 1.000 1.000 1.000 20
#> cbRcppAlgosSer 3.075 2.986 2.908 2.874 2.845 2.780 20
#> cbArrangements 3.133 2.992 2.945 2.891 2.861 3.108 20Specialized Composition Benchmarks
Similar to partitions of multisets, several composition variants
supported in RcppAlgos (e.g., distinct parts, capped cases,
etc.) are not currently available in other R packages or combinatorics
libraries. In such cases, benchmarks will only report timings for
RcppAlgos, which can serve as a reference point for future
implementations and improvements.
Compositions with Specific target
t1 <- compositionsGeneral(10, 10, repetition = TRUE, target = 30)
dim(t1)
#> [1] 9091270 10
stopifnot(all(rowSums(t1) == 30))
microbenchmark(
cbRcppAlgosSer = compositionsGeneral(10, 10, repetition = TRUE, target = 30),
cbRcppAlgosPar = compositionsGeneral(10, 10, repetition = TRUE,
target = 30, nThreads = numThreads),
times = 10,
check = "identical"
)
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> cbRcppAlgosSer 57.44 57.49 59.77 59.17 61.68 63.69 10
#> cbRcppAlgosPar 17.49 17.90 23.16 21.83 25.34 39.69 10Compositions with Distinct Parts
t1 <- compositionsGeneral(50, 8)
dim(t1)
#> [1] 4677120 8
stopifnot(all(rowSums(t1) == 50))
microbenchmark(
cbRcppAlgosSer = compositionsGeneral(50, 8),
cbRcppAlgosPar = compositionsGeneral(50, 8, nThreads = numThreads),
times = 10,
check = "identical"
)
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> cbRcppAlgosSer 214.49 215.25 216.22 215.53 215.99 221.19 10
#> cbRcppAlgosPar 52.13 53.51 55.81 55.12 56.63 64.84 10Compositions with Distinct Parts & Specific
target
t1 <- compositionsGeneral(30, 7, target = 60)
dim(t1)
#> [1] 11773440 7
stopifnot(all(rowSums(t1) == 60))
microbenchmark(
cbRcppAlgosSer = compositionsGeneral(30, 7, target = 60),
cbRcppAlgosPar = compositionsGeneral(30, 7, target = 60, nThreads = numThreads),
times = 10,
check = "identical"
)
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> cbRcppAlgosSer 359.1 359.62 363.92 361.99 364.36 380.2 10
#> cbRcppAlgosPar 92.9 94.45 96.98 96.17 97.18 105.7 10Iterators
We will show one example from each category to demonstrate the
efficiency of the iterators in RcppAlgos. The results are
similar for the rest of the cases not shown.
Combinations
pkg_arrangements <- function(n, total) {
a <- icombinations(n, as.integer(n / 2))
for (i in 1:total) a$getnext()
}
pkg_RcppAlgos <- function(n, total) {
a <- comboIter(n, as.integer(n / 2))
for (i in 1:total) a@nextIter()
}
total <- comboCount(18, 9)
total
#> [1] 48620
microbenchmark(cbRcppAlgos = pkg_RcppAlgos(18, total),
cbArrangements = pkg_arrangements(18, total),
times = 15, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgos 1.00 1.0 1.00 1.00 1.00 1.00 15
#> cbArrangements 19.78 19.5 19.13 19.27 18.76 18.57 15Permutations
pkg_arrangements <- function(n, total) {
a <- ipermutations(n)
for (i in 1:total) a$getnext()
}
pkg_RcppAlgos <- function(n, total) {
a <- permuteIter(n)
for (i in 1:total) a@nextIter()
}
total <- permuteCount(8)
total
#> [1] 40320
microbenchmark(cbRcppAlgos = pkg_RcppAlgos(8, total),
cbArrangements = pkg_arrangements(8, total),
times = 15, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgos 1.00 1.00 1.00 1.00 1.00 1.00 15
#> cbArrangements 19.42 19.19 18.87 18.76 18.38 19.43 15Partitions
pkg_partitions <- function(n, total) {
a <- firstpart(n)
for (i in 1:(total - 1)) a <- nextpart(a)
}
pkg_arrangements <- function(n, total) {
a <- ipartitions(n)
for (i in 1:total) a$getnext()
}
pkg_RcppAlgos <- function(n, total) {
a <- partitionsIter(0:n, repetition = TRUE)
for (i in 1:total) a@nextIter()
}
total <- partitionsCount(0:40, repetition = TRUE)
total
#> [1] 37338
microbenchmark(cbRcppAlgos = pkg_RcppAlgos(40, total),
cbArrangements = pkg_arrangements(40, total),
cbPartitions = pkg_partitions(40, total),
times = 15, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgos 1.00 1.00 1.00 1.00 1.00 1.00 15
#> cbArrangements 15.38 15.19 14.39 15.04 13.39 13.75 15
#> cbPartitions 24.48 24.25 22.94 23.91 21.30 21.55 15Compositions
pkg_partitions <- function(n, total) {
a <- firstcomposition(n)
for (i in 1:(total - 1)) a <- nextcomposition(a, FALSE)
}
pkg_arrangements <- function(n, total) {
a <- icompositions(n)
for (i in 1:total) a$getnext()
}
pkg_RcppAlgos <- function(n, total) {
a <- compositionsIter(0:n, repetition = TRUE)
for (i in 1:total) a@nextIter()
}
total <- compositionsCount(0:15, repetition = TRUE)
total
#> [1] 16384
microbenchmark(cbRcppAlgos = pkg_RcppAlgos(15, total),
cbArrangements = pkg_arrangements(15, total),
cbPartitions = pkg_partitions(15, total),
times = 15, unit = "relative")
#> Unit: relative
#> expr min lq mean median uq max neval
#> cbRcppAlgos 1.00 1.00 1.0 1.00 1.00 1.00 15
#> cbArrangements 14.35 14.16 13.8 14.01 13.81 12.46 15
#> cbPartitions 44.92 44.54 43.8 44.08 43.62 41.54 15