This is an automated email from the git hooks/post-receive script. tille pushed a commit to branch master in repository r-cran-doparallel.
commit eb44b6673c547d92cd0d266bc22753b42c25f4e1 Author: Andreas Tille <[email protected]> Date: Mon Oct 9 16:01:46 2017 +0200 New upstream version 1.0.10 --- DESCRIPTION | 27 ++ MD5 | 18 ++ NAMESPACE | 7 + NEWS | 32 ++ R/doParallel.R | 515 ++++++++++++++++++++++++++++++++ R/zzz.R | 3 + build/vignette.rds | Bin 0 -> 240 bytes debian/README.test | 8 - debian/changelog | 25 -- debian/compat | 1 - debian/control | 27 -- debian/copyright | 29 -- debian/docs | 3 - debian/patches/series | 1 - debian/patches/unittest-no-report.patch | 20 -- debian/rules | 6 - debian/source/format | 1 - debian/tests/control | 3 - debian/tests/run-unit-test | 10 - debian/watch | 2 - demo/00Index | 1 + demo/sincParallel.R | 37 +++ inst/doc/gettingstartedParallel.R | 73 +++++ inst/doc/gettingstartedParallel.Rnw | 344 +++++++++++++++++++++ inst/doc/gettingstartedParallel.pdf | Bin 0 -> 151532 bytes inst/examples/bootParallel.R | 83 +++++ inst/unitTests/options.R | 84 ++++++ inst/unitTests/runTestSuite.sh | 46 +++ man/doParallel-package.Rd | 39 +++ man/registerDoParallel.Rd | 59 ++++ tests/doRUnit.R | 70 +++++ vignettes/gettingstartedParallel.Rnw | 344 +++++++++++++++++++++ 32 files changed, 1782 insertions(+), 136 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..30ca57c --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,27 @@ +Package: doParallel +Type: Package +Title: Foreach Parallel Adaptor for the 'parallel' Package +Version: 1.0.10 +Authors@R: c(person("Rich", "Calaway", role="cre", email="[email protected]"), + person("Revolution", "Analytics", role=c("aut", "cph")), + person("Steve", "Weston", role="aut"), + person("Dan", "Tenenbaum", role="ctb")) +Description: Provides a parallel backend for the %dopar% function using + the parallel package. +Depends: R (>= 2.14.0), foreach(>= 1.2.0), iterators(>= 1.0.0), + parallel, utils +Suggests: caret, mlbench, rpart +Enhances: compiler, RUnit +License: GPL-2 +Author: Rich Calaway [cre], + Revolution Analytics [aut, cph], + Steve Weston [aut], + Dan Tenenbaum [ctb] +Maintainer: Rich Calaway <[email protected]> +Repository: CRAN +Repository/R-Forge/Project: doparallel +Repository/R-Forge/Revision: 15 +Repository/R-Forge/DateTimeStamp: 2015-10-13 20:31:22 +Date/Publication: 2015-10-14 09:53:49 +NeedsCompilation: no +Packaged: 2015-10-13 20:46:30 UTC; rforge diff --git a/MD5 b/MD5 new file mode 100644 index 0000000..78557e9 --- /dev/null +++ b/MD5 @@ -0,0 +1,18 @@ +685f76c9510b8d41b6a311a2896888e3 *DESCRIPTION +16c1196e34ef2f64277123d8d53442f5 *NAMESPACE +d98f2f0bd408746894779f617982fdbe *NEWS +614375f4652288a8c2ed615b3a53f188 *R/doParallel.R +86f0e4745e79399332a21f661de57bbb *R/zzz.R +c9d9d7e1319bf838125f1f9b53dc16a0 *build/vignette.rds +ad6e7aeda54fa895a60fd8c0c92a39bf *demo/00Index +acd97a961dc67743d9ae85b28aa8fec1 *demo/sincParallel.R +d1d107a8aed2c92fe6efa71cbc691831 *inst/doc/gettingstartedParallel.R +bf3cfed8a81605cf697c7e1e95bd856c *inst/doc/gettingstartedParallel.Rnw +2510d1587aecb5c9aaaee0110320bc4d *inst/doc/gettingstartedParallel.pdf +0a17c88eb4ddb5c75a71bd940627f1b1 *inst/examples/bootParallel.R +f2621d4a791a20471698dfe4ceb351eb *inst/unitTests/options.R +59ecbac80339ba8a55adc7ec51ced837 *inst/unitTests/runTestSuite.sh +127e4697324d014bdf67e3e3c9ddf80f *man/doParallel-package.Rd +8f2ff4e8944398c34a7add4667cec738 *man/registerDoParallel.Rd +8a0378f6fce59830532f2880723693f0 *tests/doRUnit.R +bf3cfed8a81605cf697c7e1e95bd856c *vignettes/gettingstartedParallel.Rnw diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..fc2a413 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,7 @@ +export(registerDoParallel) +export(stopImplicitCluster) +importFrom("utils", "packageDescription", "packageName") +import(foreach) +import(iterators) +import(parallel) + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..11dd16d --- /dev/null +++ b/NEWS @@ -0,0 +1,32 @@ +NEWS/ChangeLog for doParallel +----------------------------- +1.0.9 2015-09-21 + o Bug fixes to stopImplicitCluster functionality, courtesy of Dan Tenenbaum. + +1.0.8 2014-02-25 + o Modified vignette to use no more than two workers. + +1.0.7 2014-02-01 + o Modified to work better when a foreach loop is executed + in a package (courtesy of Steve Weston) + o Added unit tests and a minimal working example + +1.0.6 2013-10-25 + o Changed foreach, iterators, and parallel from Depends to + Imports (request of Steve Weston and Stefan Schlager) + +1.0.4 2013-09-01 + o New attachExportEnv option for doParallelSNOW + o New function stopImplicitCluster to stop the implicitly created + socket cluster. + o Updated inst/unitTests/runTestSuite.sh, bug report from Michael Cheng + +1.0.3 2013-06-06 + o New preschedule option for doParallelSNOW, courtesy of Steve Weston + o Removed assignment into global environment to meet CRAN standards. + +1.0.2 2013-05-29 + o Efficiency improvements courtesy of Steve Weston + +1.0.1 2012-04-09 + o Updated to support RevoScaleR's rxExec function diff --git a/R/doParallel.R b/R/doParallel.R new file mode 100644 index 0000000..0ea51cd --- /dev/null +++ b/R/doParallel.R @@ -0,0 +1,515 @@ +# +# Copyright (c) 2008-2010, Revolution Analytics +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License (version 2) as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# A copy of the GNU General Public License is available at +# http://www.r-project.org/Licenses/ +# + +.options <- new.env(parent=emptyenv()) +.revoDoParCluster <- NULL + +# this explicitly registers a multicore parallel backend +registerDoParallel <- function(cl, cores=NULL, ...) { + opts <- list(...) + optnames <- names(opts) + if (is.null(optnames)) + optnames <- rep('', length(opts)) + + # filter out unnamed arguments with a warning + unnamed <- ! nzchar(optnames) + if (any(unnamed)) { + warning('ignoring doParallel package option(s) specified with unnamed argument') + opts <- opts[!unnamed] + optnames <- optnames[!unnamed] + } + + # filter out unrecognized options with a warning + recog <- optnames %in% c('nocompile') + if (any(!recog)) { + warning(sprintf('ignoring unrecognized doParallel package option(s): %s', + paste(optnames[!recog], collapse=', ')), call.=FALSE) + opts <- opts[recog] + optnames <- optnames[recog] + } + + # clear .options in case registerDoParallel is called multiple times + old.optnames <- ls(.options, all.names=TRUE) + rm(list=old.optnames, pos=.options) + + # set new options + for (i in seq(along=opts)) { + assign(optnames[i], opts[[i]], pos=.options) + } + + if (missing(cl) || is.numeric(cl)) { + if (.Platform$OS.type == "windows") { + if (!missing(cl) && is.numeric(cl)) { + cl <- makeCluster(cl) + } else { + if (!missing(cores) && is.numeric(cores)){ + cl <- makeCluster(cores) + } else { + cl <- makeCluster(3) + } + } + assign(".revoDoParCluster", cl, pos=.options) + reg.finalizer(.options, function(e){ + stopImplicitCluster() + }, onexit = TRUE) + setDoPar(doParallelSNOW, cl, snowinfo) + } else { + if (!missing(cl) && is.numeric(cl)) { + cores <- cl + } + # register multicore backend + setDoPar(doParallelMC, cores, mcinfo) + } + } else { + setDoPar(doParallelSNOW, cl, snowinfo) + } +} + +"stopImplicitCluster" <- function() +{ + if (exists(".revoDoParCluster", where=.options) && !is.null(.options[['.revoDoParCluster']])) { + stopCluster(.options[['.revoDoParCluster']]) + remove(".revoDoParCluster", envir=.options) + } +} + +# internal function that determines the number of workers to use +workers <- function(data) { + if ("cluster" %in% class(data)) { + length(data) + } else { + cores <- data + if (!is.null(cores)) { + # use the number specified when registering doMC + cores + } else { + cores <- getOption('cores') + if (!is.null(cores)) { + # use the number specified via the 'cores' option + cores + } else { + # use 1/2 the number detected by parallel + cores <- parallel::detectCores() + if (cores > 2) { + cores <- ceiling(cores/2) + } + cores + } + } + } +} + +# passed to setDoPar via registerDoParallel, and called by getDoParWorkers, etc +mcinfo <- function(data, item) { + switch(item, + workers=workers(data), + name='doParallelMC', + version=packageDescription('doParallel', fields='Version'), + NULL) +} + +# passed to setDoPar via registerDoParallel, and called by getDoParWorkers, etc +snowinfo <- function(data, item) { + switch(item, + workers=workers(data), + name='doParallelSNOW', + version=packageDescription('doParallel', fields='Version'), + NULL) +} + +comp <- if (getRversion() < "2.13.0") { + function(expr, ...) expr +} else { + function(expr, ...) { + if (isTRUE(.options$nocompile)) + expr + else + compiler::compile(expr, ...) + } +} + + +parSpl <- try(parallel::splitList, silent=TRUE) +## Use the "splitList" function from parallel if it's exported +## Otherwise, use the definition it had in R 3.0.2. +"splitList" <- if (inherits(parSpl, "try-error")) { + function (x, ncl) + lapply(splitIndices(length(x), ncl), function(i) x[i]) +} else { + parSpl +} + +doParallelMC <- function(obj, expr, envir, data) { + # set the default mclapply options + preschedule <- TRUE + set.seed <- TRUE + silent <- FALSE + cores <- workers(data) + + if (!inherits(obj, 'foreach')) + stop('obj must be a foreach object') + + it <- iter(obj) + argsList <- as.list(it) + accumulator <- makeAccum(it) + + # make sure all of the necessary libraries have been loaded + for (p in obj$packages) + library(p, character.only=TRUE) + + # check for multicore-specific options + options <- obj$options$multicore + if (!is.null(options)) { + nms <- names(options) + recog <- nms %in% c('preschedule', 'set.seed', 'silent', 'cores') + if (any(!recog)) + warning(sprintf('ignoring unrecognized multicore option(s): %s', + paste(nms[!recog], collapse=', ')), call.=FALSE) + + if (!is.null(options$preschedule)) { + if (!is.logical(options$preschedule) || length(options$preschedule) != 1) { + warning('preschedule must be logical value', call.=FALSE) + } else { + if (obj$verbose) + cat(sprintf('setting mc.preschedule option to %d\n', options$preschedule)) + preschedule <- options$preschedule + } + } + + if (!is.null(options$set.seed)) { + if (!is.logical(options$set.seed) || length(options$set.seed) != 1) { + warning('set.seed must be logical value', call.=FALSE) + } else { + if (obj$verbose) + cat(sprintf('setting mc.set.seed option to %d\n', options$set.seed)) + set.seed <- options$set.seed + } + } + + if (!is.null(options$silent)) { + if (!is.logical(options$silent) || length(options$silent) != 1) { + warning('silent must be logical value', call.=FALSE) + } else { + if (obj$verbose) + cat(sprintf('setting mc.silent option to %d\n', options$silent)) + silent <- options$silent + } + } + + if (!is.null(options$cores)) { + if (!is.numeric(options$cores) || length(options$cores) != 1 || + options$cores < 1) { + warning('cores must be numeric value >= 1', call.=FALSE) + } else { + if (obj$verbose) + cat(sprintf('setting mc.cores option to %d\n', options$cores)) + cores <- options$cores + } + } + } + + # define the "worker" function, compiling expr if possible + c.expr <- comp(expr, env=envir, options=list(suppressUndefined=TRUE)) + FUN <- function(args) tryCatch(eval(c.expr, envir=args, enclos=envir), + error=function(e) e) + + # execute the tasks + results <- mclapply(argsList, FUN, mc.preschedule=preschedule, + mc.set.seed=set.seed, mc.silent=silent, + mc.cores=cores) + + # call the accumulator with all of the results + tryCatch(accumulator(results, seq(along=results)), error=function(e) { + cat('error calling combine function:\n') + print(e) + NULL + }) + + # check for errors + errorValue <- getErrorValue(it) + errorIndex <- getErrorIndex(it) + + # throw an error or return the combined results + if (identical(obj$errorHandling, 'stop') && !is.null(errorValue)) { + msg <- sprintf('task %d failed - "%s"', errorIndex, + conditionMessage(errorValue)) + stop(simpleError(msg, call=expr)) + } else { + getResult(it) + } +} + +makeDotsEnv <- function(...) { + list(...) + function() NULL +} + +.doSnowGlobals <- new.env(parent=emptyenv()) + +getparentenv <- function(pkgname) { + parenv <- NULL + + # if anything goes wrong, print the error object and return + # the global environment + tryCatch({ + # pkgname is NULL in many cases, as when the foreach loop + # is executed interactively or in an R script + if (is.character(pkgname)) { + # load the specified package + if (require(pkgname, character.only=TRUE)) { + # search for any function in the package + pkgenv <- as.environment(paste0('package:', pkgname)) + for (sym in ls(pkgenv)) { + fun <- get(sym, pkgenv, inherits=FALSE) + if (is.function(fun)) { + env <- environment(fun) + if (is.environment(env)) { + parenv <- env + break + } + } + } + if (is.null(parenv)) { + stop('loaded ', pkgname, ', but parent search failed', call.=FALSE) + } else { + message('loaded ', pkgname, ' and set parent environment') + } + } + } + }, + error=function(e) { + cat(sprintf('Error getting parent environment: %s\n', + conditionMessage(e))) + }) + + # return the global environment by default + if (is.null(parenv)) globalenv() else parenv +} + +workerInit <- function(expr, exportenv, pkgname, packages, attach=FALSE) { + assign('expr', expr, .doSnowGlobals) + assign('exportenv', exportenv, .doSnowGlobals) + exportEnv <- .doSnowGlobals$exportenv + parent.env(exportEnv) <- getparentenv(pkgname) + if (attach) { + attach(exportEnv) + } + + tryCatch({ + for (p in packages) + library(p, character.only=TRUE) + + NULL # indicates success + }, + error=function(e) { + # a character string indicates an error + conditionMessage(e) + }) +} +workerCleanup <- function() { + if ("exportEnv" %in% search()) { + detach(exportEnv) + } +} + +evalWrapper <- function(args) { + lapply(names(args), function(n) assign(n, args[[n]], pos=.doSnowGlobals$exportenv)) + tryCatch(eval(.doSnowGlobals$expr, envir=.doSnowGlobals$exportenv), error=function(e) e) +} + +# This function takes the place of workerInit and evalWrapper when +# preschedule is enabled. It is executed by the master via clusterApply +# such that there is a single chunked task for each worker in the +# cluster, rather than using clusterCall to initialize the workers and +# clusterApplyLB to compute the tasks one-by-one. This strategy can be +# significantly more efficient when there are many small tasks, and is +# very similar to the default behavior of mclapply. +workerPreschedule <- function(largs, expr, exportenv, pkgname, packages) { + parent.env(exportenv) <- getparentenv(pkgname) + task <- function(args) { + lapply(names(args), function(n) assign(n, args[[n]], pos=exportenv)) + eval(expr, envir=exportenv) + } + + tryCatch({ + # load all necessary packages + for (p in packages) + library(p, character.only=TRUE) + + # execute all of the tasks + lapply(largs, task) + }, + error=function(e) { + # only one exception was thrown, but we don't know which one, + # so we'll return it for all of the tasks + lapply(seq_along(largs), function(i) e) + }) +} + +doParallelSNOW <- function(obj, expr, envir, data) { + cl <- data + preschedule <- FALSE + attachExportEnv <- FALSE + + if (!inherits(obj, 'foreach')) + stop('obj must be a foreach object') + + it <- iter(obj) + accumulator <- makeAccum(it) + + # check for snow-specific options + options <- obj$options$snow + if (!is.null(options)) { + nms <- names(options) + recog <- nms %in% c('preschedule', 'attachExportEnv') + if (any(!recog)) + warning(sprintf('ignoring unrecognized snow option(s): %s', + paste(nms[!recog], collapse=', ')), call.=FALSE) + + if (!is.null(options$preschedule)) { + if (!is.logical(options$preschedule) || + length(options$preschedule) != 1) { + warning('preschedule must be logical value', call.=FALSE) + } else { + if (obj$verbose) + cat(sprintf('bundling all tasks into %d chunks\n', length(cl))) + preschedule <- options$preschedule + } + } + if (!is.null(options$attachExportEnv)) { + if (!is.logical(options$attachExportEnv) || + length(options$attachExportEnv) != 1) { + warning('attachExportEnv must be logical value', call.=FALSE) + } else { + if (obj$verbose) + cat("attaching export environment\n") + attachExportEnv <- options$attachExportEnv + } + } + } + + # setup the parent environment by first attempting to create an environment + # that has '...' defined in it with the appropriate values + exportenv <- tryCatch({ + qargs <- quote(list(...)) + args <- eval(qargs, envir) + environment(do.call(makeDotsEnv, args)) + }, + error=function(e) { + new.env(parent=emptyenv()) + }) + noexport <- union(obj$noexport, obj$argnames) + getexports(expr, exportenv, envir, bad=noexport) + vars <- ls(exportenv) + if (obj$verbose) { + if (length(vars) > 0) { + cat('automatically exporting the following variables', + 'from the local environment:\n') + cat(' ', paste(vars, collapse=', '), '\n') + } else { + cat('no variables are automatically exported\n') + } + } + + # compute list of variables to export + export <- unique(obj$export) + ignore <- intersect(export, vars) + if (length(ignore) > 0) { + warning(sprintf('already exporting variable(s): %s', + paste(ignore, collapse=', '))) + export <- setdiff(export, ignore) + } + + # add explicitly exported variables to exportenv + if (length(export) > 0) { + if (obj$verbose) + cat(sprintf('explicitly exporting variables(s): %s\n', + paste(export, collapse=', '))) + + for (sym in export) { + if (!exists(sym, envir, inherits=TRUE)) + stop(sprintf('unable to find variable "%s"', sym)) + val <- get(sym, envir, inherits=TRUE) + if (is.function(val) && + (identical(environment(val), .GlobalEnv) || + identical(environment(val), envir))) { + # Changing this function's environment to exportenv allows it to + # access/execute any other functions defined in exportenv. This + # has always been done for auto-exported functions, and not + # doing so for explicitly exported functions results in + # functions defined in exportenv that can't call each other. + environment(val) <- exportenv + } + assign(sym, val, pos=exportenv, inherits=FALSE) + } + } + + # send exports to workers + c.expr <- comp(expr, env=envir, options=list(suppressUndefined=TRUE)) + + # packageName function added in R 3.0.0 + pkgname <- if (exists('packageName', mode='function')) + packageName(envir) + else + NULL + + if (! preschedule) { + # send exports to workers + r <- clusterCall(cl, workerInit, c.expr, exportenv, pkgname, + obj$packages, attachExportEnv) + for (emsg in r) { + if (!is.null(emsg)) + stop('worker initialization failed: ', emsg) + } + + # execute the tasks + argsList <- as.list(it) + results <- clusterApplyLB(cl, argsList, evalWrapper) + + # clean up the workers + if (attachExportEnv){ + clusterCall(cl, workerCleanup) + } + } else { + # convert argument iterator into a list of lists + argsList <- splitList(as.list(it), length(cl)) + + # execute the tasks + results <- do.call(c, clusterApply(cl, argsList, workerPreschedule, + c.expr, exportenv, pkgname, + obj$packages)) + } + + + # call the accumulator with all of the results + tryCatch(accumulator(results, seq(along=results)), error=function(e) { + cat('error calling combine function:\n') + print(e) + }) + + # check for errors + errorValue <- getErrorValue(it) + errorIndex <- getErrorIndex(it) + + # throw an error or return the combined results + if (identical(obj$errorHandling, 'stop') && !is.null(errorValue)) { + msg <- sprintf('task %d failed - "%s"', errorIndex, + conditionMessage(errorValue)) + stop(simpleError(msg, call=expr)) + } else { + getResult(it) + } +} diff --git a/R/zzz.R b/R/zzz.R new file mode 100644 index 0000000..898c9c7 --- /dev/null +++ b/R/zzz.R @@ -0,0 +1,3 @@ +".onUnload" <- function(libpath) { + stopImplicitCluster() +} diff --git a/build/vignette.rds b/build/vignette.rds new file mode 100644 index 0000000..9ffb58e Binary files /dev/null and b/build/vignette.rds differ diff --git a/debian/README.test b/debian/README.test deleted file mode 100644 index 55a9142..0000000 --- a/debian/README.test +++ /dev/null @@ -1,8 +0,0 @@ -Notes on how this package can be tested. -──────────────────────────────────────── - -To run the unit tests provided by the package you can do - - sh run-unit-test - -in this directory. diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index d00174e..0000000 --- a/debian/changelog +++ /dev/null @@ -1,25 +0,0 @@ -r-cran-doparallel (1.0.10-3) unstable; urgency=medium - - * Test-Depends and Suggests: r-cran-caret, r-cran-mlbench, r-cran-rpart - - -- Andreas Tille <[email protected]> Thu, 23 Jun 2016 08:29:43 +0200 - -r-cran-doparallel (1.0.10-2) unstable; urgency=medium - - * Fix autopkgtest (Thanks to Gordon Ball) - - -- Andreas Tille <[email protected]> Wed, 08 Jun 2016 15:17:30 +0200 - -r-cran-doparallel (1.0.10-1) unstable; urgency=medium - - * New upstream version - * cme fix dpkg-control - * fix unit autopkgtest - - -- Andreas Tille <[email protected]> Tue, 03 May 2016 22:33:20 +0200 - -r-cran-doparallel (1.0.8-1) unstable; urgency=low - - * Initial release (closes: #753293) - - -- Andreas Tille <[email protected]> Mon, 30 Jun 2014 11:47:43 +0200 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index ec63514..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/debian/control b/debian/control deleted file mode 100644 index da80250..0000000 --- a/debian/control +++ /dev/null @@ -1,27 +0,0 @@ -Source: r-cran-doparallel -Maintainer: Debian Med Packaging Team <[email protected]> -Uploaders: Andreas Tille <[email protected]> -Section: gnu-r -Testsuite: autopkgtest -Priority: optional -Build-Depends: debhelper (>= 9), - cdbs, - r-base-dev, - r-cran-iterators, - r-cran-foreach -Standards-Version: 3.9.8 -Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/R/r-cran-doparallel/trunk/ -Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/R/r-cran-doparallel/trunk/ -Homepage: http://cran.r-project.org/web/packages/doParallel - -Package: r-cran-doparallel -Architecture: any -Depends: ${shlibs:Depends}, - ${misc:Depends}, - ${R:Depends}, - r-cran-iterators, - r-cran-foreach -Suggests: r-cran-caret, r-cran-mlbench, r-cran-rpart -Description: GNU R foreach parallel adaptor for the parallel package - This GNU R package provides a parallel backend for the %dopar% function - using the parallel package. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 7f83be8..0000000 --- a/debian/copyright +++ /dev/null @@ -1,29 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: doParallel -Upstream-Contact: Revolution Analytics <[email protected]> -Source: http://cran.r-project.org/web/packages/doParallel/ - -Files: * -Copyright: 2012-2014 Revolution Analytics, Steve Weston -License: GPL-2 - -Files: debian/* -Copyright: 2014 Andreas Tille <[email protected]> -License: GPL-2 - -License: GPL-2 - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - . - On Debian systems, the complete text of the GNU General Public - License can be found in `/usr/share/common-licenses/GPL-2'. - diff --git a/debian/docs b/debian/docs deleted file mode 100644 index 3adf0d6..0000000 --- a/debian/docs +++ /dev/null @@ -1,3 +0,0 @@ -debian/README.test -debian/tests/run-unit-test -tests diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index e762fc2..0000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -unittest-no-report.patch diff --git a/debian/patches/unittest-no-report.patch b/debian/patches/unittest-no-report.patch deleted file mode 100644 index 699967e..0000000 --- a/debian/patches/unittest-no-report.patch +++ /dev/null @@ -1,20 +0,0 @@ -Description: don't write a test summary in a read-only directory -Author: Gordon Ball <[email protected]> - ---- a/tests/doRUnit.R -+++ b/tests/doRUnit.R -@@ -48,14 +48,6 @@ - ## Report to stdout and text files - cat("------------------- UNIT TEST SUMMARY ---------------------\n\n") - printTextProtocol(tests, showDetails=FALSE) -- printTextProtocol(tests, showDetails=FALSE, -- fileName=paste(pathReport, "Summary.txt", sep="")) -- printTextProtocol(tests, showDetails=TRUE, -- fileName=paste(pathReport, ".txt", sep="")) -- -- ## Report to HTML file -- printHTMLProtocol(tests, fileName=paste(pathReport, ".html", sep="")) --# printHTMLProtocol(tests, fileName=file.path(dirname(dirname(getwd())),pkg,"gsDesign-RUnit-Test-Summary.html")) #paste(pathReport, ".html", sep="")) - - ## Return stop() to cause R CMD check stop in case of - ## - failures i.e. FALSE to unit tests or diff --git a/debian/rules b/debian/rules deleted file mode 100755 index a9fa538..0000000 --- a/debian/rules +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make -f - -include /usr/share/R/debian/r-cran.mk - -install/$(package):: - chmod +x debian/$(package)/usr/lib/R/site-library/$(cranNameOrig)/unitTests/runTestSuite.sh diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/tests/control b/debian/tests/control deleted file mode 100644 index 9da42b6..0000000 --- a/debian/tests/control +++ /dev/null @@ -1,3 +0,0 @@ -Tests: run-unit-test -Depends: @, r-cran-runit, r-cran-caret, r-cran-mlbench, r-cran-rpart -Restrictions: allow-stderr diff --git a/debian/tests/run-unit-test b/debian/tests/run-unit-test deleted file mode 100644 index ae6d6bb..0000000 --- a/debian/tests/run-unit-test +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -e - -pkg=r-cran-doparallel -if [ "$ADTTMP" = "" ] ; then - ADTTMP=`mktemp -d /tmp/${pkg}-test.XXXXXX` -fi -cd $ADTTMP -cp -a /usr/share/doc/${pkg}/tests/* $ADTTMP -R --no-save < doRUnit.R -rm -f $ADTTMP/* diff --git a/debian/watch b/debian/watch deleted file mode 100644 index e794a1f..0000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -version=3 -http://cran.r-project.org/src/contrib/doParallel_([-\d.]*)\.tar\.gz diff --git a/demo/00Index b/demo/00Index new file mode 100644 index 0000000..8489066 --- /dev/null +++ b/demo/00Index @@ -0,0 +1 @@ +sincParallel computation of the sinc function diff --git a/demo/sincParallel.R b/demo/sincParallel.R new file mode 100644 index 0000000..d51eabc --- /dev/null +++ b/demo/sincParallel.R @@ -0,0 +1,37 @@ +library(doParallel) +registerDoParallel() + +# Define a function that creates an iterator that returns subvectors +ivector <- function(x, chunks) { + n <- length(x) + i <- 1 + + nextEl <- function() { + if (chunks <= 0 || n <= 0) stop('StopIteration') + m <- ceiling(n / chunks) + r <- seq(i, length=m) + i <<- i + m + n <<- n - m + chunks <<- chunks - 1 + x[r] + } + + obj <- list(nextElem=nextEl) + class(obj) <- c('abstractiter', 'iter') + obj +} + +# Define the coordinate grid and figure out how to split up the work +x <- seq(-10, 10, by=0.1) +nw <- getDoParWorkers() +cat(sprintf('Running with %d worker(s)\n', nw)) + +# Compute the value of the sinc function at each point in the grid +z <- foreach(y=ivector(x, nw), .combine=cbind) %dopar% { + y <- rep(y, each=length(x)) + r <- sqrt(x ^ 2 + y ^ 2) + matrix(10 * sin(r) / r, length(x)) +} + +# Plot the results as a perspective plot +persp(x, x, z, ylab='y', theta=30, phi=30, expand=0.5, col="lightblue") diff --git a/inst/doc/gettingstartedParallel.R b/inst/doc/gettingstartedParallel.R new file mode 100644 index 0000000..2410e01 --- /dev/null +++ b/inst/doc/gettingstartedParallel.R @@ -0,0 +1,73 @@ +### R code from vignette source 'gettingstartedParallel.Rnw' + +################################################### +### code chunk number 1: loadLibs +################################################### +library(doParallel) +cl <- makeCluster(2) +registerDoParallel(cl) +foreach(i=1:3) %dopar% sqrt(i) + + +################################################### +### code chunk number 2: gettingstartedParallel.Rnw:149-150 +################################################### +stopCluster(cl) + + +################################################### +### code chunk number 3: gettingstartedParallel.Rnw:193-196 +################################################### +library(doParallel) +cl <- makeCluster(2) +registerDoParallel(cl) + + +################################################### +### code chunk number 4: bootpar +################################################### +x <- iris[which(iris[,5] != "setosa"), c(1,5)] +trials <- 10000 + +ptime <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %dopar% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +ptime + + +################################################### +### code chunk number 5: bootseq +################################################### +stime <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %do% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +stime + + +################################################### +### code chunk number 6: getDoParWorkers +################################################### +getDoParWorkers() + + +################################################### +### code chunk number 7: getDoParName +################################################### +getDoParName() +getDoParVersion() + + +################################################### +### code chunk number 8: gettingstartedParallel.Rnw:274-275 +################################################### +stopCluster(cl) + + diff --git a/inst/doc/gettingstartedParallel.Rnw b/inst/doc/gettingstartedParallel.Rnw new file mode 100644 index 0000000..c169735 --- /dev/null +++ b/inst/doc/gettingstartedParallel.Rnw @@ -0,0 +1,344 @@ +% \VignetteIndexEntry{Getting Started with doParallel and foreach} +% \VignetteDepends{doParallel} +% \VignetteDepends{foreach} +% \VignettePackage{doParallel} +\documentclass[12pt]{article} +\usepackage{amsmath} +\usepackage[pdftex]{graphicx} +\usepackage{color} +\usepackage{xspace} +\usepackage{url} +\usepackage{fancyvrb} +\usepackage{fancyhdr} + \usepackage[ + colorlinks=true, + linkcolor=blue, + citecolor=blue, + urlcolor=blue] + {hyperref} + \usepackage{lscape} + +\usepackage{Sweave} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% define new colors for use +\definecolor{darkgreen}{rgb}{0,0.6,0} +\definecolor{darkred}{rgb}{0.6,0.0,0} +\definecolor{lightbrown}{rgb}{1,0.9,0.8} +\definecolor{brown}{rgb}{0.6,0.3,0.3} +\definecolor{darkblue}{rgb}{0,0,0.8} +\definecolor{darkmagenta}{rgb}{0.5,0,0.5} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\newcommand{\bld}[1]{\mbox{\boldmath $#1$}} +\newcommand{\shell}[1]{\mbox{$#1$}} +\renewcommand{\vec}[1]{\mbox{\bf {#1}}} + +\newcommand{\ReallySmallSpacing}{\renewcommand{\baselinestretch}{.6}\Large\normalsize} +\newcommand{\SmallSpacing}{\renewcommand{\baselinestretch}{1.1}\Large\normalsize} + +\newcommand{\halfs}{\frac{1}{2}} + +\setlength{\oddsidemargin}{-.25 truein} +\setlength{\evensidemargin}{0truein} +\setlength{\topmargin}{-0.2truein} +\setlength{\textwidth}{7 truein} +\setlength{\textheight}{8.5 truein} +\setlength{\parindent}{0.20truein} +\setlength{\parskip}{0.10truein} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\pagestyle{fancy} +\lhead{} +\chead{Getting Started with doParallel and foreach} +\rhead{} +\lfoot{} +\cfoot{} +\rfoot{\thepage} +\renewcommand{\headrulewidth}{1pt} +\renewcommand{\footrulewidth}{1pt} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\title{Getting Started with doParallel and foreach} +\author{Steve Weston\footnote{Steve Weston wrote the original version of this vignette for the doMC package. Rich Calaway +adapted the vignette for doParallel.} and Rich Calaway \\ [email protected]} + + +\begin{document} + +\maketitle + +\thispagestyle{empty} + +\section{Introduction} + +The \texttt{doParallel} package is a ``parallel backend'' for the +\texttt{foreach} package. It provides a mechanism needed to execute +\texttt{foreach} loops in parallel. The \texttt{foreach} package must +be used in conjunction with a package such as \texttt{doParallel} in order to +execute code in parallel. The user must register a parallel backend to +use, otherwise \texttt{foreach} will execute tasks sequentially, even +when the \%dopar\% operator is used.\footnote{\texttt{foreach} will +issue a warning that it is running sequentially if no parallel backend +has been registered. It will only issue this warning once, however.} + +The \texttt{doParallel} package acts as an interface between \texttt{foreach} +and the \texttt{parallel} package of R 2.14.0 and later. The \texttt{parallel} +package is essentially a merger of the \texttt{multicore} package, which was +written by Simon Urbanek, and the \texttt{snow} package, which was written +by Luke Tierney and others. The \texttt{multicore} functionality supports +multiple workers only on those operating systems that +support the \texttt{fork} system call; this excludes Windows. By default, +\texttt{doParallel} uses \texttt{multicore} functionality on Unix-like +systems and \texttt{snow} functionality on Windows. Note that +the \texttt{multicore} functionality only runs tasks on a single +computer, not a cluster of computers. However, you can use the +\texttt{snow} functionality to execute on a cluster, using Unix-like +operating systems, Windows, or even a combination. +It is pointless to use \texttt{doParallel} and \texttt{parallel} +on a machine with only one processor with a single core. To get a speed +improvement, it must run on a machine with multiple processors, multiple +cores, or both. + +\section{A word of caution} + +Because the \texttt{parallel} package in \texttt{multicore} mode +starts its workers using +\texttt{fork} without doing a subsequent \texttt{exec}, it has some +limitations. Some operations cannot be performed properly by forked +processes. For example, connection objects very likely won't work. +In some cases, this could cause an object to become corrupted, and +the R session to crash. + +\section{Registering the \texttt{doParallel} parallel backend} + +To register \texttt{doParallel} to be used with \texttt{foreach}, you must +call the \texttt{registerDoParallel} function. If you call this with no +arguments, on Windows you will get three workers and on Unix-like +systems you will get a number of workers equal to approximately half the +number of cores on your system. You can also specify a cluster +(as created by the \texttt{makeCluster} function) or a number of cores. +The \texttt{cores} argument specifies the number of worker +processes that \texttt{doParallel} will use to execute tasks, which will +by default be +equal to one-half the total number of cores on the machine. You don't need to +specify a value for it, however. By default, \texttt{doParallel} will use the +value of the ``cores'' option, as specified with +the standard ``options'' function. If that isn't set, then +\texttt{doParallel} will try to detect the number of cores, and use one-half +that many workers. + +Remember: unless \texttt{registerDoMC} is called, \texttt{foreach} will +{\em not} run in parallel. Simply loading the \texttt{doParallel} package is +not enough. + +\section{An example \texttt{doParallel} session} + +Before we go any further, let's load \texttt{doParallel}, register it, and use +it with \texttt{foreach}. We will use \texttt{snow}-like functionality in this +vignette, so we start by loading the package and starting a cluster: + +<<loadLibs>>= +library(doParallel) +cl <- makeCluster(2) +registerDoParallel(cl) +foreach(i=1:3) %dopar% sqrt(i) +@ +<<echo=FALSE>>= +stopCluster(cl) +@ + +To use \texttt{multicore}-like functionality, we would specify the number +of cores to use instead (but note that on Windows, attempting to use more +than one core with \texttt{parallel} results in an error): +\begin{verbatim} +library(doParallel} +registerDoParallel(cores=2) +foreach(i=1:3) %dopar% sqrt(i) +\end{verbatim} + +\begin{quote} +Note well that this is {\em not} a practical use of \texttt{doParallel}. This +is our ``Hello, world'' program for parallel computing. It tests that +everything is installed and set up properly, but don't expect it to run +faster than a sequential \texttt{for} loop, because it won't! +\texttt{sqrt} executes far too quickly to be worth executing in +parallel, even with a large number of iterations. With small tasks, the +overhead of scheduling the task and returning the result can be greater +than the time to execute the task itself, resulting in poor performance. +In addition, this example doesn't make use of the vector capabilities of +\texttt{sqrt}, which it must to get decent performance. This is just a +test and a pedagogical example, {\em not} a benchmark. +\end{quote} + +But returning to the point of this example, you can see that it is very +simple to load \texttt{doParallel} with all of its dependencies +(\texttt{foreach}, \texttt{iterators}, \texttt{parallel}, etc), and to +register it. For the rest of the R session, whenever you execute +\texttt{foreach} with \texttt{\%dopar\%}, the tasks will be executed +using \texttt{doParallel} and \texttt{parallel}. Note that you can register +a different parallel backend later, or deregister \texttt{doParallel} by +registering the sequential backend by calling the \texttt{registerDoSEQ} +function. + +\section{A more serious example} + +Now that we've gotten our feet wet, let's do something a bit less +trivial. One good example is bootstrapping. Let's see how long it +takes to run 10,000 bootstrap iterations in parallel on +\Sexpr{getDoParWorkers()} cores: + +<<echo=FALSE>>= +library(doParallel) +cl <- makeCluster(2) +registerDoParallel(cl) +@ +<<bootpar>>= +x <- iris[which(iris[,5] != "setosa"), c(1,5)] +trials <- 10000 + +ptime <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %dopar% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +ptime +@ + +Using \texttt{doParallel} and \texttt{parallel} we were able to perform +10,000 bootstrap iterations in \Sexpr{ptime} seconds on +\Sexpr{getDoParWorkers()} cores. By changing the \texttt{\%dopar\%} to +\texttt{\%do\%}, we can run the same code sequentially to determine the +performance improvement: + +<<bootseq>>= +stime <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %do% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +stime +@ + + +The sequential version ran in \Sexpr{stime} seconds, which means the +speed up is about \Sexpr{round(stime / ptime, digits=1)} on +\Sexpr{getDoParWorkers()} workers.\footnote{If you build this vignette +yourself, you can see how well this problem runs on your hardware. None +of the times are hardcoded in this document. You can also run the same +example which is in the examples directory of the \texttt{doParallel} +distribution.} Ideally, the speed up would be \Sexpr{getDoParWorkers()}, +but no multicore CPUs are ideal, and neither are the operating systems +and software that run on them. + +At any rate, this is a more realistic example that is worth executing in +parallel. We do not explain what it's doing or how it works +here. We just want to give you something more substantial than the +\texttt{sqrt} example in case you want to run some benchmarks yourself. +You can also run this example on a cluster by simply reregistering +with a cluster object that specifies the nodes to use. (See the +\texttt{makeCluster} help file for more details.) + +\section{Getting information about the parallel backend} + +To find out how many workers \texttt{foreach} is going to use, you can +use the \texttt{getDoParWorkers} function: + +<<getDoParWorkers>>= +getDoParWorkers() +@ + +This is a useful sanity check that you're actually running in parallel. +If you haven't registered a parallel backend, or if your machine only +has one core, \texttt{getDoParWorkers} will return one. In either case, +don't expect a speed improvement. \texttt{foreach} is clever, but it +isn't magic. + +The \texttt{getDoParWorkers} function is also useful when you want the +number of tasks to be equal to the number of workers. You may want to +pass this value to an iterator constructor, for example. + +You can also get the name and version of the currently registered +backend: + +<<getDoParName>>= +getDoParName() +getDoParVersion() +@ +<<echo=FALSE>>= +stopCluster(cl) +@ +This is mostly useful for documentation purposes, or for checking that +you have the most recent version of \texttt{doParallel}. + +\section{Specifying multicore options} + +When using \texttt{multicore}-like functionality, the \texttt{doParallel} package allows +you to specify various options when +running \texttt{foreach} that are supported by the underlying +\texttt{mclapply} function: ``preschedule'', ``set.seed'', ``silent'', +and ``cores''. You can learn about these options from the +\texttt{mclapply} man page. They are set using the \texttt{foreach} +\texttt{.options.multicore} argument. Here's an example of how to do +that: + +\begin{verbatim} +mcoptions <- list(preschedule=FALSE, set.seed=FALSE) +foreach(i=1:3, .options.multicore=mcoptions) %dopar% sqrt(i) +\end{verbatim} + +The ``cores'' options allows you to temporarily override the number of +workers to use for a single \texttt{foreach} operation. This is more +convenient than having to re-register \texttt{doParallel}. Although if no +value of ``cores'' was specified when \texttt{doParallel} was registered, you +can also change this value dynamically using the \texttt{options} +function: + +\begin{verbatim} +options(cores=2) +getDoParWorkers() +options(cores=3) +getDoParWorkers() +\end{verbatim} + +If you did specify the number of cores when registering \texttt{doParallel}, +the ``cores'' option is ignored: + +\begin{verbatim} +registerDoParallel(4) +options(cores=2) +getDoParWorkers() +\end{verbatim} + +As you can see, there are a number of options for controlling the number +of workers to use with \texttt{parallel}, but the default behaviour +usually does what you want. + +\section{Stopping your cluster} + +If you are using \texttt{snow}-like functionality, you will want to stop your +cluster when you are done using it. The \texttt{doParallel} package's +\texttt{.onUnload} function will do this automatically if the cluster was created +automatically by \texttt{registerDoParallel}, but if you created the cluster manually +you should stop it using the \texttt{stopCluster} function: + +\begin{verbatim} +stopCluster(cl) +\end{verbatim} + +\section{Conclusion} + +The \texttt{doParallel} and \texttt{parallel} packages provide a nice, +efficient parallel programming platform for multiprocessor/multicore +computers running operating systems such as Linux and Mac OS X. It is +very easy to install, and very easy to use. In short order, an average +R programmer can start executing parallel programs, without any previous +experience in parallel computing. + +\end{document} diff --git a/inst/doc/gettingstartedParallel.pdf b/inst/doc/gettingstartedParallel.pdf new file mode 100644 index 0000000..58ae617 Binary files /dev/null and b/inst/doc/gettingstartedParallel.pdf differ diff --git a/inst/examples/bootParallel.R b/inst/examples/bootParallel.R new file mode 100644 index 0000000..45cd14e --- /dev/null +++ b/inst/examples/bootParallel.R @@ -0,0 +1,83 @@ +suppressMessages(library(doParallel)) +cl <- makePSOCKcluster(4) +registerDoParallel(cl) + +cat(sprintf('doParallel %s\n', packageVersion('doParallel'))) +junk <- matrix(0, 1000000, 8) +cat(sprintf('Size of extra junk data: %d bytes\n', object.size(junk))) + +x <- iris[which(iris[,5] != "setosa"), c(1,5)] + +trials <- 10000 + +ptime <- system.time({ + r <- foreach(icount(trials), .combine=cbind, + .export='junk') %dopar% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +cat(sprintf('parallel foreach: %6.1f sec\n', ptime)) + +ptime2 <- system.time({ + snowopts <- list(preschedule=TRUE) + r <- foreach(icount(trials), .combine=cbind, + .export='junk', .options.snow=snowopts) %dopar% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +cat(sprintf('parallel foreach with prescheduling: %6.1f sec\n', ptime2)) + + +ptime3 <- system.time({ + chunks <- getDoParWorkers() + r <- foreach(n=idiv(trials, chunks=chunks), .combine=cbind, + .export='junk') %dopar% { + y <- lapply(seq_len(n), function(i) { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + }) + do.call('cbind', y) + } +})[3] +cat(sprintf('chunked parallel foreach: %6.1f sec\n', ptime3)) + +ptime4 <- system.time({ + mkworker <- function(x, junk) { + force(x) + force(junk) + function(i) { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } + } + y <- parLapply(cl, seq_len(trials), mkworker(x, junk)) + r <- do.call('cbind', y) +})[3] +cat(sprintf('parLapply: %6.1f sec\n', ptime4)) + +stime <- system.time({ + y <- lapply(seq_len(trials), function(i) { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + }) + r <- do.call('cbind', y) +})[3] +cat(sprintf('sequential lapply: %6.1f sec\n', stime)) + +stime2 <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %do% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +cat(sprintf('sequential foreach: %6.1f sec\n', stime2)) + +stopCluster(cl) diff --git a/inst/unitTests/options.R b/inst/unitTests/options.R new file mode 100644 index 0000000..f1865b3 --- /dev/null +++ b/inst/unitTests/options.R @@ -0,0 +1,84 @@ +test.preschedule <- function() { + x <- list(1:3, 1:9, 1:19) + cs <- 1:20 + dpn <- getDoParName() + + for (chunkSize in cs) { + ## preschedule is TRUE for MC by default and + ## FALSE for SNOW, so we test by setting them otherwise + if (identical(dpn, "doParallelMC")) { + opts <- list(preschedule=FALSE) + } else { + opts <- list(preschedule=TRUE) + } + for (y in x) { + if (identical(dpn, "doParallelMC")) { + actual <- foreach(i=y, .options.multicore=opts) %dopar% i + } + else { + actual <- foreach(i=y, .options.snow=opts) %dopar% i + } + checkEquals(actual, as.list(y)) + if (identical(dpn, "doParallelMC")) { + actual <- foreach(i=y, .combine="c", .options.multicore=opts) %dopar% i + } + else { + actual <- foreach(i=y, .combine="c", .options.snow=opts) %dopar% i + } + checkEquals(actual, y) + } + } +} + +test.attach <- function() { + if (identical(getDoParName(), "doParallelMC")) { + return(TRUE) + } else { + myFun <- function(x){ + myFun1(x+1) + } + myFun1 <- function(x){ + 2*x + } + testFun <- function(){ + inRes1 <- checkTrue("exportEnv" %in% search()) + if (!inRes1) { + stop("Attaching exportEnv failed") + } + inRes2 <- checkTrue(exists("myFun1", where=2)) + if (!inRes1) { + stop("myFun1 not found in exportEnv") + } + myFun(1) + } + res <- suppressWarnings(foreach(i=1:4, .combine="c", .packages="RUnit", + .export="myFun1", .options.snow=list(attachExportEnv=TRUE)) %dopar% testFun()) + + checkEquals(res, c(4,4, 4, 4)) + } +} + +pkgname.test.stress <- function() { + if (!require(caret, quietly=TRUE)) { + return(TRUE) + } else { + library(mlbench) + data(BostonHousing) + + lmFit <- train(medv ~ . + rm:lstat, + data = BostonHousing, + "lm") + + library(rpart) + rpartFit <- train(medv ~ ., + data = BostonHousing, + "rpart", + tuneLength = 9) + } +} + +"test.pkgname.test.stress" <- function() +{ + res <- try(pkgname.test.stress()) + checkTrue(!is(res, "try-error"), msg="pkgname stress test failed") +} \ No newline at end of file diff --git a/inst/unitTests/runTestSuite.sh b/inst/unitTests/runTestSuite.sh new file mode 100644 index 0000000..5d2804f --- /dev/null +++ b/inst/unitTests/runTestSuite.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +LOGFILE=test.log + +R --vanilla --slave > ${LOGFILE} 2>&1 <<'EOF' +library(doParallel) +library(RUnit) + +verbose <- as.logical(Sys.getenv('FOREACH_VERBOSE', 'FALSE')) + +library(doParallel) +registerDoParallel() + +options(warn=1) +options(showWarnCalls=TRUE) + +cat('Starting test at', date(), '\n') +cat(sprintf('doParallel version: %s\n', getDoParVersion())) +cat(sprintf('Running with %d worker(s)\n', getDoParWorkers())) + +tests <- c('options.R') + +errcase <- list() +for (f in tests) { + cat('\nRunning test file:', f, '\n') + t <- runTestFile(f) + e <- getErrors(t) + if (e$nErr + e$nFail > 0) { + errcase <- c(errcase, t) + print(t) + } +} + +if (length(errcase) == 0) { + cat('*** Ran all tests successfully ***\n') +} else { + cat('!!! Encountered', length(errcase), 'problems !!!\n') + for (t in errcase) { + print(t) + } +} + +stopImplicitCluster() + +cat('Finished test at', date(), '\n') +EOF diff --git a/man/doParallel-package.Rd b/man/doParallel-package.Rd new file mode 100644 index 0000000..e9aa4bf --- /dev/null +++ b/man/doParallel-package.Rd @@ -0,0 +1,39 @@ +\name{doParallel-package} +\alias{doParallel-package} +\alias{doParallel} +\docType{package} +\title{ +The doParallel Package +} +\description{ +The doParallel package provides a parallel backend for the foreach/\%dopar\% +function using the \code{parallel} package of R 2.14.0 and later. +} +\details{ +Further information is available in the following help topics: +\tabular{ll}{ +\code{registerDoParallel} \tab register doParallel to be used by foreach/\%dopar\%\cr +} + +To see a tutorial introduction to the doParallel package, +use \code{vignette("gettingstartedParallel")}. To see a tutorial +introduction to the foreach package, use \code{vignette("foreach")}. + +To see a demo of doParallel computing the sinc function, +use \code{demo(sincParallel)}. + +Some examples (in addition to those in the help pages) are included in +the ``examples'' directory of the doParallel package. To list the files in +the examples directory, +use \code{list.files(system.file("examples", package="doParallel"))}. +To run the bootstrap example, use +\code{source(system.file("examples", "bootParallel.R", package="doParallel"))}. +This is a simple benchmark, executing both sequentally and in parallel. +There are many more examples that come with the foreach package, +which will work with the doParallel package if it is registered as the +parallel backend. + +For a complete list of functions with individual help pages, +use \code{library(help="doParallel")}. +} +\keyword{package} diff --git a/man/registerDoParallel.Rd b/man/registerDoParallel.Rd new file mode 100644 index 0000000..09e505c --- /dev/null +++ b/man/registerDoParallel.Rd @@ -0,0 +1,59 @@ +\name{registerDoParallel} +\alias{registerDoParallel} +\alias{stopImplicitCluster} +\title{registerDoParallel} +\description{ +The \code{registerDoParallel} function is used to register the +parallel backend with the \code{foreach} package. +} +\usage{ +registerDoParallel(cl, cores=NULL, \dots) +stopImplicitCluster() +} +\arguments{ + \item{cl}{A cluster object as returned by \code{makeCluster}, or the number + of nodes to be created in the cluster. If not specified, on Windows a + three worker cluster is created and used.} + \item{cores}{The number of cores to use for parallel execution. If not + specified, the number of cores is set to the value of + \code{options("cores")}, if specified, or to one-half the number of cores detected + by the \code{parallel} package.} + \item{\dots}{Package options. Currently, only the \code{nocompile} option + is supported. If \code{nocompile} is set to \code{TRUE}, compiler + support is disabled.} +} +\details{ +The \code{parallel} package from R 2.14.0 and later provides functions for +parallel execution of R code on machines with multiple cores or processors +or multiple computers. It is essentially a blend of the \code{snow} and +\code{multicore} packages. By default, the \code{doParallel} package uses +\code{snow}-like functionality. The \code{snow}-like functionality +should work fine on Unix-like systems, but the \code{multicore}-like +functionality is limited to a single sequential worker on Windows systems. +On workstations with multiple cores running Unix-like operating systems, +the system \code{fork} call is used to spawn copies of the current process. + +The \code{doParallel} backend supports both multicore and snow options passed +through the \code{foreach} function. +The supported multicore options are \code{preschedule}, \code{set.seed}, +\code{silent}, and \code{cores}, which are analogous to the similarly named +arguments to \code{\link{mclapply}}, and are passed using the +\code{.options.multicore} argument to \code{foreach}. The supported snow options are +\code{preschedule}, which like its multicore analog can be used to chunk the +tasks so that each worker gets a prescheduled chunk of tasks, and +\code{attachExportEnv}, which can be used to attach the export environment +in certain cases where R's lexical scoping is unable to find a needed +export. The snow options are passed to \code{foreach} using the \code{.options.snow} +argument. + +The function \code{stopImplicitCluster} can be used in vignettes and other places +where it is important to explicitly close the implicitly created cluster. +} +\examples{ +cl <- makePSOCKcluster(2) +registerDoParallel(cl) +m <- matrix(rnorm(9), 3, 3) +foreach(i=1:nrow(m), .combine=rbind) %dopar% (m[i,] / mean(m[i,])) +stopCluster(cl) +} +\keyword{utilities} diff --git a/tests/doRUnit.R b/tests/doRUnit.R new file mode 100644 index 0000000..752cfa9 --- /dev/null +++ b/tests/doRUnit.R @@ -0,0 +1,70 @@ +## unit tests will not be done if RUnit is not available +if(require("RUnit", quietly=TRUE)) { + + ## --- Setup --- + + pkg <- "doParallel" # <-- Change to package name! + + if(Sys.getenv("RCMDCHECK") == "FALSE") { + ## Path to unit tests for standalone running under Makefile (not R CMD check) + ## PKG/tests/../inst/unitTests + path <- file.path(getwd(), "..", "inst", "unitTests") + } else { + ## Path to unit tests for R CMD check + ## PKG.Rcheck/tests/../PKG/unitTests + path <- system.file(package=pkg, "unitTests") + } + cat("\nRunning unit tests\n") + print(list(pkg=pkg, getwd=getwd(), pathToUnitTests=path)) + + library(package=pkg, character.only=TRUE) + ################################################################ + ## BEGIN PACKAGE SPECIFIC CONFIGURATION # + ################################################################ + registerDoParallel(2) + ################################################################ + ## END PACKAGE SPECIFIC CONFIGURATION # + ################################################################ + + ## If desired, load the name space to allow testing of private functions + ## if (is.element(pkg, loadedNamespaces())) + ## attach(loadNamespace(pkg), name=paste("namespace", pkg, sep=":"), pos=3) + ## + ## or simply call PKG:::myPrivateFunction() in tests + + + + ## --- Testing --- + + ## Define tests + testSuite <- defineTestSuite(name=paste(pkg, "unit testing"), + dirs=path, testFileRegexp = "^options\\.R$") + ## Run + tests <- runTestSuite(testSuite) + + ## Default report name + pathReport <- file.path(path, "report") + + ## Report to stdout and text files + cat("------------------- UNIT TEST SUMMARY ---------------------\n\n") + printTextProtocol(tests, showDetails=FALSE) + printTextProtocol(tests, showDetails=FALSE, + fileName=paste(pathReport, "Summary.txt", sep="")) + printTextProtocol(tests, showDetails=TRUE, + fileName=paste(pathReport, ".txt", sep="")) + + ## Report to HTML file + printHTMLProtocol(tests, fileName=paste(pathReport, ".html", sep="")) +# printHTMLProtocol(tests, fileName=file.path(dirname(dirname(getwd())),pkg,"gsDesign-RUnit-Test-Summary.html")) #paste(pathReport, ".html", sep="")) + + ## Return stop() to cause R CMD check stop in case of + ## - failures i.e. FALSE to unit tests or + ## - errors i.e. R errors + tmp <- getErrors(tests) + if(tmp$nFail > 0 | tmp$nErr > 0) { + stop(paste("\n\nunit testing failed (#test failures: ", tmp$nFail, + ", #R errors: ", tmp$nErr, ")\n\n", sep="")) + } +} else { + warning("cannot run unit tests -- package RUnit is not available") +} diff --git a/vignettes/gettingstartedParallel.Rnw b/vignettes/gettingstartedParallel.Rnw new file mode 100644 index 0000000..c169735 --- /dev/null +++ b/vignettes/gettingstartedParallel.Rnw @@ -0,0 +1,344 @@ +% \VignetteIndexEntry{Getting Started with doParallel and foreach} +% \VignetteDepends{doParallel} +% \VignetteDepends{foreach} +% \VignettePackage{doParallel} +\documentclass[12pt]{article} +\usepackage{amsmath} +\usepackage[pdftex]{graphicx} +\usepackage{color} +\usepackage{xspace} +\usepackage{url} +\usepackage{fancyvrb} +\usepackage{fancyhdr} + \usepackage[ + colorlinks=true, + linkcolor=blue, + citecolor=blue, + urlcolor=blue] + {hyperref} + \usepackage{lscape} + +\usepackage{Sweave} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% define new colors for use +\definecolor{darkgreen}{rgb}{0,0.6,0} +\definecolor{darkred}{rgb}{0.6,0.0,0} +\definecolor{lightbrown}{rgb}{1,0.9,0.8} +\definecolor{brown}{rgb}{0.6,0.3,0.3} +\definecolor{darkblue}{rgb}{0,0,0.8} +\definecolor{darkmagenta}{rgb}{0.5,0,0.5} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\newcommand{\bld}[1]{\mbox{\boldmath $#1$}} +\newcommand{\shell}[1]{\mbox{$#1$}} +\renewcommand{\vec}[1]{\mbox{\bf {#1}}} + +\newcommand{\ReallySmallSpacing}{\renewcommand{\baselinestretch}{.6}\Large\normalsize} +\newcommand{\SmallSpacing}{\renewcommand{\baselinestretch}{1.1}\Large\normalsize} + +\newcommand{\halfs}{\frac{1}{2}} + +\setlength{\oddsidemargin}{-.25 truein} +\setlength{\evensidemargin}{0truein} +\setlength{\topmargin}{-0.2truein} +\setlength{\textwidth}{7 truein} +\setlength{\textheight}{8.5 truein} +\setlength{\parindent}{0.20truein} +\setlength{\parskip}{0.10truein} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\pagestyle{fancy} +\lhead{} +\chead{Getting Started with doParallel and foreach} +\rhead{} +\lfoot{} +\cfoot{} +\rfoot{\thepage} +\renewcommand{\headrulewidth}{1pt} +\renewcommand{\footrulewidth}{1pt} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\title{Getting Started with doParallel and foreach} +\author{Steve Weston\footnote{Steve Weston wrote the original version of this vignette for the doMC package. Rich Calaway +adapted the vignette for doParallel.} and Rich Calaway \\ [email protected]} + + +\begin{document} + +\maketitle + +\thispagestyle{empty} + +\section{Introduction} + +The \texttt{doParallel} package is a ``parallel backend'' for the +\texttt{foreach} package. It provides a mechanism needed to execute +\texttt{foreach} loops in parallel. The \texttt{foreach} package must +be used in conjunction with a package such as \texttt{doParallel} in order to +execute code in parallel. The user must register a parallel backend to +use, otherwise \texttt{foreach} will execute tasks sequentially, even +when the \%dopar\% operator is used.\footnote{\texttt{foreach} will +issue a warning that it is running sequentially if no parallel backend +has been registered. It will only issue this warning once, however.} + +The \texttt{doParallel} package acts as an interface between \texttt{foreach} +and the \texttt{parallel} package of R 2.14.0 and later. The \texttt{parallel} +package is essentially a merger of the \texttt{multicore} package, which was +written by Simon Urbanek, and the \texttt{snow} package, which was written +by Luke Tierney and others. The \texttt{multicore} functionality supports +multiple workers only on those operating systems that +support the \texttt{fork} system call; this excludes Windows. By default, +\texttt{doParallel} uses \texttt{multicore} functionality on Unix-like +systems and \texttt{snow} functionality on Windows. Note that +the \texttt{multicore} functionality only runs tasks on a single +computer, not a cluster of computers. However, you can use the +\texttt{snow} functionality to execute on a cluster, using Unix-like +operating systems, Windows, or even a combination. +It is pointless to use \texttt{doParallel} and \texttt{parallel} +on a machine with only one processor with a single core. To get a speed +improvement, it must run on a machine with multiple processors, multiple +cores, or both. + +\section{A word of caution} + +Because the \texttt{parallel} package in \texttt{multicore} mode +starts its workers using +\texttt{fork} without doing a subsequent \texttt{exec}, it has some +limitations. Some operations cannot be performed properly by forked +processes. For example, connection objects very likely won't work. +In some cases, this could cause an object to become corrupted, and +the R session to crash. + +\section{Registering the \texttt{doParallel} parallel backend} + +To register \texttt{doParallel} to be used with \texttt{foreach}, you must +call the \texttt{registerDoParallel} function. If you call this with no +arguments, on Windows you will get three workers and on Unix-like +systems you will get a number of workers equal to approximately half the +number of cores on your system. You can also specify a cluster +(as created by the \texttt{makeCluster} function) or a number of cores. +The \texttt{cores} argument specifies the number of worker +processes that \texttt{doParallel} will use to execute tasks, which will +by default be +equal to one-half the total number of cores on the machine. You don't need to +specify a value for it, however. By default, \texttt{doParallel} will use the +value of the ``cores'' option, as specified with +the standard ``options'' function. If that isn't set, then +\texttt{doParallel} will try to detect the number of cores, and use one-half +that many workers. + +Remember: unless \texttt{registerDoMC} is called, \texttt{foreach} will +{\em not} run in parallel. Simply loading the \texttt{doParallel} package is +not enough. + +\section{An example \texttt{doParallel} session} + +Before we go any further, let's load \texttt{doParallel}, register it, and use +it with \texttt{foreach}. We will use \texttt{snow}-like functionality in this +vignette, so we start by loading the package and starting a cluster: + +<<loadLibs>>= +library(doParallel) +cl <- makeCluster(2) +registerDoParallel(cl) +foreach(i=1:3) %dopar% sqrt(i) +@ +<<echo=FALSE>>= +stopCluster(cl) +@ + +To use \texttt{multicore}-like functionality, we would specify the number +of cores to use instead (but note that on Windows, attempting to use more +than one core with \texttt{parallel} results in an error): +\begin{verbatim} +library(doParallel} +registerDoParallel(cores=2) +foreach(i=1:3) %dopar% sqrt(i) +\end{verbatim} + +\begin{quote} +Note well that this is {\em not} a practical use of \texttt{doParallel}. This +is our ``Hello, world'' program for parallel computing. It tests that +everything is installed and set up properly, but don't expect it to run +faster than a sequential \texttt{for} loop, because it won't! +\texttt{sqrt} executes far too quickly to be worth executing in +parallel, even with a large number of iterations. With small tasks, the +overhead of scheduling the task and returning the result can be greater +than the time to execute the task itself, resulting in poor performance. +In addition, this example doesn't make use of the vector capabilities of +\texttt{sqrt}, which it must to get decent performance. This is just a +test and a pedagogical example, {\em not} a benchmark. +\end{quote} + +But returning to the point of this example, you can see that it is very +simple to load \texttt{doParallel} with all of its dependencies +(\texttt{foreach}, \texttt{iterators}, \texttt{parallel}, etc), and to +register it. For the rest of the R session, whenever you execute +\texttt{foreach} with \texttt{\%dopar\%}, the tasks will be executed +using \texttt{doParallel} and \texttt{parallel}. Note that you can register +a different parallel backend later, or deregister \texttt{doParallel} by +registering the sequential backend by calling the \texttt{registerDoSEQ} +function. + +\section{A more serious example} + +Now that we've gotten our feet wet, let's do something a bit less +trivial. One good example is bootstrapping. Let's see how long it +takes to run 10,000 bootstrap iterations in parallel on +\Sexpr{getDoParWorkers()} cores: + +<<echo=FALSE>>= +library(doParallel) +cl <- makeCluster(2) +registerDoParallel(cl) +@ +<<bootpar>>= +x <- iris[which(iris[,5] != "setosa"), c(1,5)] +trials <- 10000 + +ptime <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %dopar% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +ptime +@ + +Using \texttt{doParallel} and \texttt{parallel} we were able to perform +10,000 bootstrap iterations in \Sexpr{ptime} seconds on +\Sexpr{getDoParWorkers()} cores. By changing the \texttt{\%dopar\%} to +\texttt{\%do\%}, we can run the same code sequentially to determine the +performance improvement: + +<<bootseq>>= +stime <- system.time({ + r <- foreach(icount(trials), .combine=cbind) %do% { + ind <- sample(100, 100, replace=TRUE) + result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit)) + coefficients(result1) + } +})[3] +stime +@ + + +The sequential version ran in \Sexpr{stime} seconds, which means the +speed up is about \Sexpr{round(stime / ptime, digits=1)} on +\Sexpr{getDoParWorkers()} workers.\footnote{If you build this vignette +yourself, you can see how well this problem runs on your hardware. None +of the times are hardcoded in this document. You can also run the same +example which is in the examples directory of the \texttt{doParallel} +distribution.} Ideally, the speed up would be \Sexpr{getDoParWorkers()}, +but no multicore CPUs are ideal, and neither are the operating systems +and software that run on them. + +At any rate, this is a more realistic example that is worth executing in +parallel. We do not explain what it's doing or how it works +here. We just want to give you something more substantial than the +\texttt{sqrt} example in case you want to run some benchmarks yourself. +You can also run this example on a cluster by simply reregistering +with a cluster object that specifies the nodes to use. (See the +\texttt{makeCluster} help file for more details.) + +\section{Getting information about the parallel backend} + +To find out how many workers \texttt{foreach} is going to use, you can +use the \texttt{getDoParWorkers} function: + +<<getDoParWorkers>>= +getDoParWorkers() +@ + +This is a useful sanity check that you're actually running in parallel. +If you haven't registered a parallel backend, or if your machine only +has one core, \texttt{getDoParWorkers} will return one. In either case, +don't expect a speed improvement. \texttt{foreach} is clever, but it +isn't magic. + +The \texttt{getDoParWorkers} function is also useful when you want the +number of tasks to be equal to the number of workers. You may want to +pass this value to an iterator constructor, for example. + +You can also get the name and version of the currently registered +backend: + +<<getDoParName>>= +getDoParName() +getDoParVersion() +@ +<<echo=FALSE>>= +stopCluster(cl) +@ +This is mostly useful for documentation purposes, or for checking that +you have the most recent version of \texttt{doParallel}. + +\section{Specifying multicore options} + +When using \texttt{multicore}-like functionality, the \texttt{doParallel} package allows +you to specify various options when +running \texttt{foreach} that are supported by the underlying +\texttt{mclapply} function: ``preschedule'', ``set.seed'', ``silent'', +and ``cores''. You can learn about these options from the +\texttt{mclapply} man page. They are set using the \texttt{foreach} +\texttt{.options.multicore} argument. Here's an example of how to do +that: + +\begin{verbatim} +mcoptions <- list(preschedule=FALSE, set.seed=FALSE) +foreach(i=1:3, .options.multicore=mcoptions) %dopar% sqrt(i) +\end{verbatim} + +The ``cores'' options allows you to temporarily override the number of +workers to use for a single \texttt{foreach} operation. This is more +convenient than having to re-register \texttt{doParallel}. Although if no +value of ``cores'' was specified when \texttt{doParallel} was registered, you +can also change this value dynamically using the \texttt{options} +function: + +\begin{verbatim} +options(cores=2) +getDoParWorkers() +options(cores=3) +getDoParWorkers() +\end{verbatim} + +If you did specify the number of cores when registering \texttt{doParallel}, +the ``cores'' option is ignored: + +\begin{verbatim} +registerDoParallel(4) +options(cores=2) +getDoParWorkers() +\end{verbatim} + +As you can see, there are a number of options for controlling the number +of workers to use with \texttt{parallel}, but the default behaviour +usually does what you want. + +\section{Stopping your cluster} + +If you are using \texttt{snow}-like functionality, you will want to stop your +cluster when you are done using it. The \texttt{doParallel} package's +\texttt{.onUnload} function will do this automatically if the cluster was created +automatically by \texttt{registerDoParallel}, but if you created the cluster manually +you should stop it using the \texttt{stopCluster} function: + +\begin{verbatim} +stopCluster(cl) +\end{verbatim} + +\section{Conclusion} + +The \texttt{doParallel} and \texttt{parallel} packages provide a nice, +efficient parallel programming platform for multiprocessor/multicore +computers running operating systems such as Linux and Mac OS X. It is +very easy to install, and very easy to use. In short order, an average +R programmer can start executing parallel programs, without any previous +experience in parallel computing. + +\end{document} -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-doparallel.git _______________________________________________ debian-med-commit mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/debian-med-commit
