vignettes/articles/Parallel-processing.Rmd
Parallel-processing.RmdThis vignette shows how to speed up RunCanek() with the
ncores parameter, which parallelizes the mutual nearest
neighbor (MNN) search. We use the same ifnb dataset as Seurat’s
own integration tutorial — stimulated vs. control PBMCs — so if
you’ve worked through that one, this data will look familiar. For a
general walkthrough of RunCanek() itself, see the main Seurat vignette; this one focuses
specifically on ncores.
ifnb is loaded from the SeuratData package
rather than CRAN, so it needs a one-time install the first time you use
it:
if (!requireNamespace("SeuratData", quietly = TRUE)) {
install.packages("remotes")
remotes::install_github("satijalab/seurat-data")
}
SeuratData::InstallData("ifnb")
library(Canek)
library(Seurat)
#> Loading required package: SeuratObject
#> Loading required package: sp
#>
#> Attaching package: 'SeuratObject'
#> The following objects are masked from 'package:base':
#>
#> intersect, t
library(SeuratData)
#> ── Installed datasets ──────────────────────────────── SeuratData v0.2.2.9002 ──
#> ✔ ifnb 3.1.0 ✔ panc8 3.0.2
#> ────────────────────────────────────── Key ─────────────────────────────────────
#> ✔ Dataset loaded successfully
#> ❯ Dataset built with a newer version of Seurat than installed
#> ❓ Unknown version of Seurat installedStandard log-normalization workflow, same preprocessing as the main Seurat vignette.
ifnb <- LoadData("ifnb")
#> Validating object structure
#> Updating object slots
#> Ensuring keys are in the proper structure
#> Warning: Assay RNA changing from Assay to Assay
#> Ensuring keys are in the proper structure
#> Ensuring feature names don't have underscores or pipes
#> Updating slots in RNA
#> Validating object structure for Assay 'RNA'
#> Object representation is consistent with the most current Seurat version
#> Warning: Assay RNA changing from Assay to Assay5
ifnb[["RNA"]] <- split(ifnb[["RNA"]], f = ifnb$stim)
ifnb <- NormalizeData(ifnb, verbose = FALSE)
ifnb <- FindVariableFeatures(ifnb, verbose = FALSE)
ifnb <- ScaleData(ifnb, verbose = FALSE)
ifnb <- RunPCA(ifnb, verbose = FALSE)ncores parameter
Finding MNN pairs requires two independent k-nearest-neighbor
searches — one from reference to query, one from query to reference.
Neither depends on the other’s result, so they can run at the same time
instead of one after the other. Setting ncores > 1 does
exactly that, via parallel::mclapply().
ncores defaults to 1 (fully sequential)
— parallelism is opt-in only, never automatic. Canek never tries to
detect how many cores are “available” and use them on its own to avoid
any issues when using a shared cluster or HPC job. Silently using them
could oversubscribe your allocation and could affect other jobs sharing
the same node. Set ncores explicitly to however many cores
you know are free to use.
Two things ncores > 1 checks for you, rather than
silently doing something unexpected:
parallel::mclapply() relies
on fork(), which Windows doesn’t support. Requesting
ncores > 1 there raises an error rather than quietly
running sequentially anyway and leaving you wondering why it isn’t any
faster.ncores exceeds parallel::detectCores(),
RunCanek() errors instead of silently reducing it to
whatever’s available.
set.seed(42)
system.time(
ifnb <- RunCanek(ifnb, "stim")
)
#> user system elapsed
#> 25.965 0.120 21.738
set.seed(42)
system.time(
ifnb <- RunCanek(ifnb, "stim", ncores = 3, integration.name = "CanekParallel")
)
#> Warning: Key 'Canek_' taken, using 'canekparallel_' instead
#> user system elapsed
#> 24.511 0.925 14.221How much this actually helps depends on how much of the total runtime is spent in the parallelized MNN search versus everything else (clustering, fuzzy correction, etc.).
Parallelizing the two searches doesn’t change what they compute.
unname() clears each reduction’s column names before
comparing: Seurat renames a reduction’s key prefix when two reductions
would otherwise share one (here, "Canek_" becomes
"canekparallel_" for the second run), which would otherwise
make all.equal() return FALSE even though every number in
the embeddings matches.
isTRUE(all.equal(
unname(Embeddings(ifnb, "canek")),
unname(Embeddings(ifnb, "canekparallel"))
))
#> [1] TRUE
sessionInfo()
#> R version 4.4.3 (2025-02-28)
#> Platform: x86_64-conda-linux-gnu
#> Running under: Ubuntu 22.04.2 LTS
#>
#> Matrix products: default
#> BLAS/LAPACK: /home/mloza/miniconda3/envs/canek-check/lib/libopenblasp-r0.3.33.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
#> [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
#> [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
#> [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Asia/Tokyo
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] panc8.SeuratData_3.0.2 ifnb.SeuratData_3.1.0 SeuratData_0.2.2.9002
#> [4] Seurat_5.5.1 SeuratObject_5.4.0 sp_2.2-1
#> [7] Canek_0.3.1
#>
#> loaded via a namespace (and not attached):
#> [1] deldir_2.0-4 pbapply_1.7-4 gridExtra_2.3.1
#> [4] rlang_1.3.0 magrittr_2.0.5 RcppAnnoy_0.0.23
#> [7] otel_0.2.0 spatstat.geom_3.8-1 matrixStats_1.5.0
#> [10] ggridges_0.5.7 compiler_4.4.3 flexmix_2.3-20
#> [13] png_0.1-9 vctrs_0.7.3 reshape2_1.4.5
#> [16] stringr_1.6.0 crayon_1.5.3 pkgconfig_2.0.3
#> [19] fastmap_1.2.0 promises_1.5.0 numbers_0.9-2
#> [22] purrr_1.2.2 xfun_0.60 modeltools_0.2-24
#> [25] bluster_1.16.0 jsonlite_2.0.0 goftest_1.2-3
#> [28] later_1.4.8 spatstat.utils_3.2-4 fpc_2.2-14
#> [31] BiocParallel_1.40.0 irlba_2.3.7 parallel_4.4.3
#> [34] prabclus_2.3-5 cluster_2.1.8.2 R6_2.6.1
#> [37] ica_1.0-3 spatstat.data_3.1-9 stringi_1.8.7
#> [40] RColorBrewer_1.1-3 reticulate_1.46.0 spatstat.univar_3.2-0
#> [43] parallelly_1.48.0 lmtest_0.9-40 diptest_0.77-2
#> [46] scattermore_1.2 Rcpp_1.1.2 knitr_1.51
#> [49] tensor_1.5.1 future.apply_1.20.2 zoo_1.8-15
#> [52] sctransform_0.4.3 FNN_1.1.4.1 httpuv_1.6.17
#> [55] Matrix_1.7-5 splines_4.4.3 nnet_7.3-20
#> [58] igraph_2.3.3 tidyselect_1.2.1 abind_1.4-8
#> [61] spatstat.random_3.4-5 spatstat.explore_3.8-0 codetools_0.2-20
#> [64] miniUI_0.1.2 listenv_1.0.0 plyr_1.8.9
#> [67] lattice_0.22-9 tibble_3.3.1 shiny_1.14.0
#> [70] S7_0.2.2 ROCR_1.0-12 evaluate_1.0.5
#> [73] Rtsne_0.17 future_1.70.0 fastDummies_1.7.6
#> [76] survival_3.8-9 polyclip_1.10-7 fitdistrplus_1.2-6
#> [79] mclust_6.1.3 kernlab_0.9-33 pillar_1.11.1
#> [82] KernSmooth_2.23-26 stats4_4.4.3 plotly_4.12.0
#> [85] generics_0.1.4 RcppHNSW_0.7.0 S4Vectors_0.44.0
#> [88] ggplot2_4.0.3 scales_1.4.0 globals_0.19.1
#> [91] xtable_1.8-8 class_7.3-23 glue_1.8.1
#> [94] lazyeval_0.2.3 tools_4.4.3 BiocNeighbors_2.0.0
#> [97] robustbase_0.99-7 data.table_1.18.4 RSpectra_0.16-2
#> [100] RANN_2.6.2 dotCall64_1.2 cowplot_1.2.0
#> [103] grid_4.4.3 tidyr_1.3.2 nlme_3.1-169
#> [106] patchwork_1.3.2 cli_3.6.6 rappdirs_0.3.4
#> [109] spatstat.sparse_3.2-0 spam_2.11-4 viridisLite_0.4.3
#> [112] dplyr_1.2.1 uwot_0.2.4 gtable_0.3.6
#> [115] DEoptimR_1.2-0 digest_0.6.39 progressr_1.0.0
#> [118] BiocGenerics_0.52.0 ggrepel_0.9.8 htmlwidgets_1.6.4
#> [121] farver_2.1.2 htmltools_0.5.9 lifecycle_1.0.5
#> [124] httr_1.4.8 mime_0.13 MASS_7.3-66