Skip to content

Commit fd98a34

Browse files
Now availableCores() memoizes the values of all its components
1 parent 1f45686 commit fd98a34

File tree

7 files changed

+443
-297
lines changed

7 files changed

+443
-297
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: parallelly
2-
Version: 1.42.0-9002
2+
Version: 1.42.0-9003
33
Title: Enhancing the 'parallel' Package
44
Imports:
55
parallel,

NEWS.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Version (development version)
22

3+
## Significant Changes
4+
5+
* Now `availableCores()` memoizes the values of all its components.
6+
This means that as soon as it has been called, environment variables
7+
such as `NSLOTS` will no longer be queried.
8+
39
## Documentation
410

5-
* Add more help on the R option `parallelly.maxWorkers.localhost`
6-
limits. Improved the warning and error messages that are produced
7-
when these settings are exceeded.
11+
* Add more help on the R option `parallelly.maxWorkers.localhost`
12+
limits. Improved the warning and error messages that are produced
13+
when these settings are exceeded.
814

915

1016
# Version 1.42.0 [2025-01-30]

R/availableCores.R

Lines changed: 139 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -438,36 +438,50 @@ availableCores <- function(constraints = NULL, methods = getOption2("parallelly.
438438
} # availableCores()
439439

440440

441-
getNproc <- function(ignore = c("OMP_NUM_THREADS", "OMP_THREAD_LIMIT")) {
442-
## 'nproc' is limited by 'OMP_NUM_THREADS' and 'OMP_THREAD_LIMIT', if set.
443-
## However, that is not what we want for availableCores(). Because of
444-
## this, we unset those while querying 'nproc'.
445-
if (length(ignore) > 0) {
446-
ignore <- intersect(ignore, names(Sys.getenv()))
441+
getNproc <- local({
442+
res <- NULL
443+
444+
function(ignore = c("OMP_NUM_THREADS", "OMP_THREAD_LIMIT")) {
445+
if (!is.null(res)) return(res)
446+
447+
## 'nproc' is limited by 'OMP_NUM_THREADS' and 'OMP_THREAD_LIMIT', if set.
448+
## However, that is not what we want for availableCores(). Because of
449+
## this, we unset those while querying 'nproc'.
447450
if (length(ignore) > 0) {
448-
oignore <- Sys.getenv(ignore, names = TRUE)
449-
oignore <- as.list(oignore)
450-
on.exit(do.call(Sys.setenv, args = oignore), add = TRUE)
451-
Sys.unsetenv(ignore)
451+
ignore <- intersect(ignore, names(Sys.getenv()))
452+
if (length(ignore) > 0) {
453+
oignore <- Sys.getenv(ignore, names = TRUE)
454+
oignore <- as.list(oignore)
455+
on.exit(do.call(Sys.setenv, args = oignore), add = TRUE)
456+
Sys.unsetenv(ignore)
457+
}
458+
}
459+
460+
systems <- list(linux = "nproc 2>/dev/null")
461+
os <- names(systems)
462+
m <- pmatch(os, table = R.version$os, nomatch = NA_integer_)
463+
m <- os[!is.na(m)]
464+
if (length(m) == 0L) {
465+
res <<- NA_integer_
466+
return(res)
452467
}
453-
}
454468

455-
systems <- list(linux = "nproc 2>/dev/null")
456-
os <- names(systems)
457-
m <- pmatch(os, table = R.version$os, nomatch = NA_integer_)
458-
m <- os[!is.na(m)]
459-
if (length(m) == 0L) return(NA_integer_)
460-
461-
for (cmd in systems[[m]]) {
462-
tryCatch({
463-
res <- suppressWarnings(system(cmd, intern=TRUE))
464-
res <- gsub("(^[[:space:]]+|[[:space:]]+$)", "", res[1])
465-
if (grepl("^[[:digit:]]+$", res)) return(as.integer(res))
466-
}, error = identity)
469+
for (cmd in systems[[m]]) {
470+
tryCatch({
471+
value <- suppressWarnings(system(cmd, intern=TRUE))
472+
value <- gsub("(^[[:space:]]+|[[:space:]]+$)", "", value[1])
473+
if (grepl("^[[:digit:]]+$", value)) {
474+
res <<- as.integer(value)
475+
return(res)
476+
}
477+
}, error = identity)
478+
}
479+
480+
res <<- NA_integer_
481+
482+
res
467483
}
468-
469-
NA_integer_
470-
}
484+
})
471485

472486

473487
checkNumberOfLocalWorkers <- function(workers) {
@@ -534,103 +548,122 @@ getopt_int <- function(name, mode = "integer") {
534548
# High-Performance Compute (HPC) Schedulers
535549
# --------------------------------------------------------------------------
536550
## Number of slots assigned by LSF
537-
availableCoresLSF <- function() {
538-
n <- getenv_int("LSB_DJOB_NUMPROC")
539-
n
540-
}
551+
availableCoresLSF <- local({
552+
n <- NULL
553+
function() {
554+
if (!is.null(n)) return(n)
555+
n <<- getenv_int("LSB_DJOB_NUMPROC")
556+
n
557+
}
558+
})
541559

542560

543561
## Number of cores assigned by TORQUE/PBS
544-
availableCoresPBS <- function() {
545-
n <- getenv_int("PBS_NUM_PPN")
546-
if (is.na(n)) {
547-
## PBSPro sets 'NCPUS' but not 'PBS_NUM_PPN'
548-
n <- getenv_int("NCPUS")
562+
availableCoresPBS <- local({
563+
n <- NULL
564+
function() {
565+
n <<- getenv_int("PBS_NUM_PPN")
566+
if (is.na(n)) {
567+
## PBSPro sets 'NCPUS' but not 'PBS_NUM_PPN'
568+
n <<- getenv_int("NCPUS")
569+
}
570+
n
549571
}
550-
n
551-
}
572+
})
552573

553574

554575
## Number of slots assigned by Fujitsu Technical Computing Suite
555576
## We choose to call this job scheduler "PJM" based on the prefix
556577
## it's environment variables use.
557-
availableCoresPJM <- function() {
558-
## PJM_VNODE_CORE: e.g. pjsub -L vnode-core=8
559-
## "This environment variable is set only when virtual nodes
560-
## are allocated, and it is not set when nodes are allocated."
561-
n <- getenv_int("PJM_VNODE_CORE")
562-
if (is.na(n)) {
563-
## PJM_PROC_BY_NODE: e.g. pjsub -L vnode-core=8
564-
## "Maximum number of processes that are generated per node by
565-
## an MPI program. However, if a single node (node=1) or virtual
566-
## node (vnode=1) is allocated and the mpi option of the pjsub
567-
## command is not specified, this environment variable is not set."
568-
n <- getenv_int("PJM_PROC_BY_NODE")
578+
availableCoresPJM <- local({
579+
n <- NULL
580+
function() {
581+
## PJM_VNODE_CORE: e.g. pjsub -L vnode-core=8
582+
## "This environment variable is set only when virtual nodes
583+
## are allocated, and it is not set when nodes are allocated."
584+
n <<- getenv_int("PJM_VNODE_CORE")
585+
if (is.na(n)) {
586+
## PJM_PROC_BY_NODE: e.g. pjsub -L vnode-core=8
587+
## "Maximum number of processes that are generated per node by
588+
## an MPI program. However, if a single node (node=1) or virtual
589+
## node (vnode=1) is allocated and the mpi option of the pjsub
590+
## command is not specified, this environment variable is not set."
591+
n <<- getenv_int("PJM_PROC_BY_NODE")
592+
}
593+
n
569594
}
570-
n
571-
}
595+
})
572596

573597

574598
## Number of cores assigned by Oracle/Son/Sun/Univa Grid Engine (SGE/UGE)
575-
availableCoresSGE <- function() {
576-
n <- getenv_int("NSLOTS")
577-
n
578-
}
599+
availableCoresSGE <- local({
600+
n <- NULL
601+
function() {
602+
n <<- getenv_int("NSLOTS")
603+
n
604+
}
605+
})
579606

580607

581608
## Number of cores assigned by Slurm
582-
availableCoresSlurm <- function() {
583-
## The assumption is that the following works regardless of
584-
## number of nodes requested /HB 2020-09-18
585-
## Example: --cpus-per-task={n}
586-
n <- getenv_int("SLURM_CPUS_PER_TASK")
587-
if (is.na(n)) {
588-
## Example: --nodes={nnodes} (defaults to 1, short: -N {nnodes})
589-
## From 'man sbatch':
590-
## SLURM_JOB_NUM_NODES (and SLURM_NNODES for backwards compatibility)
591-
## Total number of nodes in the job's resource allocation.
592-
nnodes <- getenv_int("SLURM_JOB_NUM_NODES")
593-
if (is.na(nnodes)) nnodes <- getenv_int("SLURM_NNODES")
594-
if (is.na(nnodes)) nnodes <- 1L ## Can this happen? /HB 2020-09-18
595-
596-
if (nnodes == 1L) {
597-
## Example: --nodes=1 --ntasks={n} (short: -n {n})
598-
## IMPORTANT: 'SLURM_CPUS_ON_NODE' appears to be rounded up when nodes > 1.
599-
## Example 1: With --nodes=2 --cpus-per-task=3 we see SLURM_CPUS_ON_NODE=4
600-
## although SLURM_CPUS_PER_TASK=3.
601-
## Example 2: With --nodes=2 --ntasks=7, we see SLURM_CPUS_ON_NODE=6,
602-
## SLURM_JOB_CPUS_PER_NODE=6,2, no SLURM_CPUS_PER_TASK, and
603-
## SLURM_TASKS_PER_NODE=5,2.
604-
## Conclusions: We can only use 'SLURM_CPUS_ON_NODE' for nnodes = 1.
605-
n <- getenv_int("SLURM_CPUS_ON_NODE")
606-
} else {
607-
## Parse `SLURM_TASKS_PER_NODE`
608-
nodecounts <- getenv_int("SLURM_TASKS_PER_NODE", mode = "character")
609-
if (!is.na(nodecounts)) {
610-
## Examples:
611-
## SLURM_TASKS_PER_NODE=5,2
612-
## SLURM_TASKS_PER_NODE=2(x2),1(x3) # Source: 'man sbatch'
613-
n <- slurm_expand_nodecounts(nodecounts)
614-
if (anyNA(n)) return(NA_real_)
615-
616-
## ASSUMPTION: We assume that it is the first component on the list that
617-
## corresponds to the current machine. /HB 2021-03-05
618-
n <- n[1]
609+
availableCoresSlurm <- local({
610+
n <- NULL
611+
function() {
612+
## The assumption is that the following works regardless of
613+
## number of nodes requested /HB 2020-09-18
614+
## Example: --cpus-per-task={n}
615+
n <<- getenv_int("SLURM_CPUS_PER_TASK")
616+
if (is.na(n)) {
617+
## Example: --nodes={nnodes} (defaults to 1, short: -N {nnodes})
618+
## From 'man sbatch':
619+
## SLURM_JOB_NUM_NODES (and SLURM_NNODES for backwards compatibility)
620+
## Total number of nodes in the job's resource allocation.
621+
nnodes <- getenv_int("SLURM_JOB_NUM_NODES")
622+
if (is.na(nnodes)) nnodes <- getenv_int("SLURM_NNODES")
623+
if (is.na(nnodes)) nnodes <- 1L ## Can this happen? /HB 2020-09-18
624+
625+
if (nnodes == 1L) {
626+
## Example: --nodes=1 --ntasks={n} (short: -n {n})
627+
## IMPORTANT: 'SLURM_CPUS_ON_NODE' appears to be rounded up when nodes > 1.
628+
## Example 1: With --nodes=2 --cpus-per-task=3 we see SLURM_CPUS_ON_NODE=4
629+
## although SLURM_CPUS_PER_TASK=3.
630+
## Example 2: With --nodes=2 --ntasks=7, we see SLURM_CPUS_ON_NODE=6,
631+
## SLURM_JOB_CPUS_PER_NODE=6,2, no SLURM_CPUS_PER_TASK, and
632+
## SLURM_TASKS_PER_NODE=5,2.
633+
## Conclusions: We can only use 'SLURM_CPUS_ON_NODE' for nnodes = 1.
634+
n <<- getenv_int("SLURM_CPUS_ON_NODE")
635+
} else {
636+
## Parse `SLURM_TASKS_PER_NODE`
637+
nodecounts <- getenv_int("SLURM_TASKS_PER_NODE", mode = "character")
638+
if (!is.na(nodecounts)) {
639+
## Examples:
640+
## SLURM_TASKS_PER_NODE=5,2
641+
## SLURM_TASKS_PER_NODE=2(x2),1(x3) # Source: 'man sbatch'
642+
n <<- slurm_expand_nodecounts(nodecounts)
643+
if (anyNA(n)) {
644+
n <<- NA_real_
645+
return(n)
646+
}
647+
648+
## ASSUMPTION: We assume that it is the first component on the list that
649+
## corresponds to the current machine. /HB 2021-03-05
650+
n <<- n[1]
651+
}
619652
}
620653
}
621-
}
622-
623-
## TODO?: Can we validate above assumptions/results? /HB 2020-09-18
624-
if (FALSE && !is.na(n)) {
625-
## Is any of the following useful?
654+
655+
## TODO?: Can we validate above assumptions/results? /HB 2020-09-18
656+
if (FALSE && !is.na(n)) {
657+
## Is any of the following useful?
658+
659+
## Example: --ntasks={ntasks} (no default, short: -n {ntasks})
660+
## From 'man sbatch':
661+
## SLURM_NTASKS (and SLURM_NPROCS for backwards compatibility)
662+
## Same as -n, --ntasks
663+
ntasks <- getenv_int("SLURM_NTASKS")
664+
if (is.na(ntasks)) ntasks <- getenv_int("SLURM_NPROCS")
665+
}
626666

627-
## Example: --ntasks={ntasks} (no default, short: -n {ntasks})
628-
## From 'man sbatch':
629-
## SLURM_NTASKS (and SLURM_NPROCS for backwards compatibility)
630-
## Same as -n, --ntasks
631-
ntasks <- getenv_int("SLURM_NTASKS")
632-
if (is.na(ntasks)) ntasks <- getenv_int("SLURM_NPROCS")
667+
n
633668
}
634-
635-
n
636-
} ## availableCoresSlurm()
669+
}) ## availableCoresSlurm()

0 commit comments

Comments
 (0)