Last updated: 2024-12-24
Checks: 7 0
Knit directory: muse/
This reproducible R Markdown analysis was created with workflowr (version 1.7.1). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.
Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.
Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.
The command set.seed(20200712)
was run prior to running
the code in the R Markdown file. Setting a seed ensures that any results
that rely on randomness, e.g. subsampling or permutations, are
reproducible.
Great job! Recording the operating system, R version, and package versions is critical for reproducibility.
Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.
Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.
Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.
The results in this page were generated with repository version 5871c7c. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.
Note that you need to be careful to ensure that all relevant files for
the analysis have been committed to Git prior to generating the results
(you can use wflow_publish
or
wflow_git_commit
). workflowr only checks the R Markdown
file, but you know if there are other scripts or data files that it
depends on. Below is the status of the Git repository when the results
were generated:
Ignored files:
Ignored: .Rhistory
Ignored: .Rproj.user/
Ignored: data/pbmc3k.csv
Ignored: data/pbmc3k.csv.gz
Ignored: data/pbmc3k/
Ignored: r_packages_4.4.0/
Ignored: r_packages_4.4.1/
Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.
These are the previous versions of the repository in which changes were
made to the R Markdown (analysis/parallel.Rmd
) and HTML
(docs/parallel.html
) files. If you’ve configured a remote
Git repository (see ?wflow_git_remote
), click on the
hyperlinks in the table below to view the files as they were in that
past version.
File | Version | Author | Date | Message |
---|---|---|---|---|
Rmd | 5871c7c | Dave Tang | 2024-12-24 | Using future_lapply() |
html | ebb6bb6 | Dave Tang | 2023-12-20 | Build site. |
Rmd | 7df7cc7 | Dave Tang | 2023-12-20 | MulticoreParam |
html | c9ebb81 | Dave Tang | 2023-12-20 | Build site. |
Rmd | 16e8bbf | Dave Tang | 2023-12-20 | Forking is faster than using sockets |
html | b874727 | Dave Tang | 2023-12-20 | Build site. |
Rmd | 49be9e8 | Dave Tang | 2023-12-20 | Update |
html | 0b6b70f | Dave Tang | 2023-07-27 | Build site. |
Rmd | e6c246e | Dave Tang | 2023-07-27 | Worker environment |
html | 2f4cb47 | Dave Tang | 2023-07-26 | Build site. |
Rmd | 9367b80 | Dave Tang | 2023-07-26 | pbapply |
html | 130d11f | Dave Tang | 2022-11-17 | Build site. |
Rmd | b2043f3 | Dave Tang | 2022-11-17 | Parallel computation in R |
As stated in the foreach vignette:
Much of parallel computing comes to doing three things: splitting the problem into pieces, executing the pieces in parallel, and combining the results back together.
There are several packages that make it easy to run tasks in parallel:
foreach
package and
acts as an interface between foreach
and the
parallel
package.system.time
From ?proc.time
:
The “user time” is the CPU time charged for the execution of user instructions of the calling process.
The “system time” is the CPU time charged for execution by the system on behalf of the calling process.
Elapsed time is the amount of time that has elapsed/passed. The
user
and system
time while sleeping is close
to zero because the CPU is idly waiting and not executing anything.
system.time(
Sys.sleep(5)
)
user system elapsed
0.000 0.000 5.002
More information is provided on Stack Overflow:
“User CPU time” gives the CPU time spent by the current process (i.e., the current R session and outside the kernel)
“System CPU time” gives the CPU time spent by the kernel (the operating system) on behalf of the current process. The operating system is used for things like opening files, doing input or output, starting other processes, and looking at the system clock: operations that involve resources that many processes must share.
Create a list of 100 data frames each with 5,000 observations across 100 variables.
create_df <- function(n, m, seed = 1984){
set.seed(seed)
as.data.frame(
matrix(
data = rnorm(n = n * m),
nrow = n,
ncol = m
)
)
}
my_list <- lapply(1:100, function(x) create_df(5000, 100, x))
length(my_list)
[1] 100
This is a parameterised notebook; the number of threads used for the code examples is 4.
params$threads
[1] 4
parallel
Load the parallel
package.
library(parallel)
Create a summary of each variable in each data frame without parallelisation.
system.time(
my_sum <- lapply(my_list, summary)
)
user system elapsed
3.475 0.004 3.478
The mclapply
function can be used to process a list in
parallel. Note that this function uses forking, which is not available
on Windows.
system.time(
my_sum_mc <- mclapply(my_list, summary, mc.cores = params$threads)
)
user system elapsed
0.020 0.016 1.034
Compare the two summaries.
identical(my_sum, my_sum_mc)
[1] TRUE
Another way to run the jobs in parallel is via sockets. For Windows
users, you will need to use this method for parallelisation. In
addition, you need to use the parLapply
function instead of
mclapply
.
cl <- makeCluster(params$threads)
system.time(
my_sum_sock <- parLapply(cl, my_list, summary)
)
user system elapsed
0.388 0.098 2.029
stopCluster(cl)
identical(my_sum_mc, my_sum_sock)
[1] TRUE
Note that forking is faster.
If you run the code below:
cl <- makeCluster(4)
system.time(
test <- parLapply(cl, 1:4, function(x){
class(my_list)
})
)
stopCluster(cl)
you will get the following error:
Error in checkForRemoteErrors(val) :
4 nodes produced errors; first error: object 'my_list' not found
This is because each worker is using a different environment. To make
the my_list
object available to each worker, we use the
clusterExport()
function.
cl <- makeCluster(4)
clusterExport(cl, list("my_list"))
system.time(
test2 <- parSapply(cl, 1:4, function(x){
class(my_list)
})
)
user system elapsed
0.001 0.000 0.001
stopCluster(cl)
test2
[1] "list" "list" "list" "list"
pbapply
Parallelisation with a progress bar! From the help page of
pblapply
:
Parallel processing can be enabled through the cl argument. parLapply is called when cl is a ‘cluster’ object, mclapply is called when cl is an integer. Showing the progress bar increases the communication overhead between the main process and nodes / child processes compared to the parallel equivalents of the functions without the progress bar. The functions fall back to their original equivalents when the progress bar is disabled (i.e. getOption(“pboptions”)$type == “none” or dopb() is FALSE). This is the default when interactive() if FALSE (i.e. called from command line R script).
library(pbapply)
cl <- makeCluster(params$threads)
system.time(
my_sum_pb <- pblapply(my_list, summary, cl = cl)
)
user system elapsed
0.409 0.076 2.039
stopCluster(cl)
identical(my_sum_mc, my_sum_pb)
[1] TRUE
Use mclapply
.
system.time(
my_sum_pb_fork <- pblapply(my_list, summary, cl = params$threads)
)
user system elapsed
0.013 0.020 1.038
identical(my_sum_pb, my_sum_pb_fork)
[1] TRUE
doParallel
Load the doParallel
package.
library(doParallel)
Loading required package: foreach
Loading required package: iterators
Using foreach
.
cl <- makeCluster(params$threads)
registerDoParallel(cl)
system.time(
my_sum_dopar <- foreach(l = my_list) %dopar% {
summary(l)
}
)
user system elapsed
0.491 0.061 2.861
stopCluster(cl)
identical(my_sum_mc, my_sum_dopar)
[1] TRUE
BiocParallel
Load BiocParallel
.
library(BiocParallel)
Using bplapply
.
param <- SnowParam(workers = params$threads, type = "SOCK")
system.time(
my_sum_bp <- bplapply(my_list, summary, BPPARAM = param)
)
user system elapsed
0.488 0.088 4.636
identical(my_sum_mc, my_sum_bp)
[1] TRUE
Forking.
param <- SnowParam(workers = params$threads, type = "FORK")
system.time(
my_sum_bp_fork <- bplapply(my_list, summary, BPPARAM = param)
)
user system elapsed
0.148 0.119 1.678
identical(my_sum_bp, my_sum_bp_fork)
[1] TRUE
Using MulticoreParam
.
param <- MulticoreParam(workers = params$threads, progressbar = FALSE)
system.time(
my_sum_bp_mc <- bplapply(my_list, summary, BPPARAM = param)
)
user system elapsed
0.997 0.112 1.076
identical(my_sum_bp_fork, my_sum_bp_mc)
[1] TRUE
furrr
Load required libraries.
library(furrr)
Loading required package: future
library(purrr)
Attaching package: 'purrr'
The following objects are masked from 'package:foreach':
accumulate, when
Map without parallelisation.
system.time(
my_sum_pur <- map(my_list, summary)
)
user system elapsed
3.521 0.027 3.548
identical(my_sum_mc, my_sum_pur)
[1] TRUE
Map with parallelisation.
plan(multisession, workers = params$threads)
system.time(
my_sum_fur <- future_map(my_list, summary)
)
user system elapsed
0.223 0.125 2.374
identical(my_sum_pur, my_sum_fur)
[1] TRUE
future
Load required libraries.
library(future)
library(future.apply)
Map with parallelisation using future_lapply()
.
plan(multisession, workers = params$threads)
system.time(
my_sum_future_lapply <- future_lapply(my_list, summary)
)
user system elapsed
0.200 0.139 2.199
identical(my_sum, my_sum_future_lapply)
[1] TRUE
So, which package should you use? BiocParallel
and
furrr
are tailored for use with Bioconductor and
purrr
, so use those packages accordingly.
For parallelisation over a list, use parallel
. The foreach
function provides more flexibility when parallelising, so use the
doParallel
package if you have a more complicated task.
Lastly, forking is faster than using sockets. If you’re not using Windows, consider using forking over sockets.
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.5 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.20.so; LAPACK version 3.10.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] parallel stats graphics grDevices utils datasets methods
[8] base
other attached packages:
[1] future.apply_1.11.3 purrr_1.0.2 furrr_0.3.1
[4] future_1.34.0 BiocParallel_1.40.0 doParallel_1.0.17
[7] iterators_1.0.14 foreach_1.5.2 pbapply_1.7-2
[10] workflowr_1.7.1
loaded via a namespace (and not attached):
[1] sass_0.4.9 utf8_1.2.4 stringi_1.8.4 listenv_0.9.1
[5] digest_0.6.37 magrittr_2.0.3 evaluate_1.0.1 fastmap_1.2.0
[9] rprojroot_2.0.4 jsonlite_1.8.9 processx_3.8.4 whisker_0.4.1
[13] ps_1.8.1 promises_1.3.0 httr_1.4.7 fansi_1.0.6
[17] codetools_0.2-20 jquerylib_0.1.4 cli_3.6.3 rlang_1.1.4
[21] parallelly_1.38.0 cachem_1.1.0 yaml_2.3.10 tools_4.4.1
[25] httpuv_1.6.15 globals_0.16.3 vctrs_0.6.5 R6_2.5.1
[29] lifecycle_1.0.4 git2r_0.35.0 stringr_1.5.1 fs_1.6.4
[33] pkgconfig_2.0.3 callr_3.7.6 pillar_1.9.0 bslib_0.8.0
[37] later_1.3.2 glue_1.8.0 Rcpp_1.0.13 xfun_0.48
[41] tibble_3.2.1 rstudioapi_0.17.1 knitr_1.48 htmltools_0.5.8.1
[45] snow_0.4-4 rmarkdown_2.28 compiler_4.4.1 getPass_0.2-4