1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
|
% \VignetteIndexEntry{Nesting Foreach Loops}
% \VignetteDepends{foreach}
% \VignettePackage{foreach}
\documentclass[12pt]{article}
\usepackage{amsmath}
\usepackage[pdftex]{graphicx}
\usepackage{color}
\usepackage{xspace}
\usepackage{fancyvrb}
\usepackage{fancyhdr}
\usepackage[
colorlinks=true,
linkcolor=blue,
citecolor=blue,
urlcolor=blue]
{hyperref}
\usepackage{lscape}
\usepackage{Sweave}
\usepackage{float}
\floatstyle{plain}
\newfloat{example}{thp}{lop}
\floatname{example}{Example}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 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{Nesting {\tt Foreach} Loops}
\rhead{}
\lfoot{}
\cfoot{}
\rfoot{\thepage}
\renewcommand{\headrulewidth}{1pt}
\renewcommand{\footrulewidth}{1pt}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\title{Nesting {\tt Foreach} Loops}
\author{Steve Weston \\ steve@revolution-computing.com}
\begin{document}
\maketitle
\thispagestyle{empty}
\section{Introduction}
<<loadLibs,echo=FALSE,results=hide>>=
library(foreach)
@
The \texttt{foreach} package provides a looping construct for executing
R code repeatedly. It is similar to the standard \texttt{for} loop,
which makes it easy to convert a \texttt{for} loop to a \texttt{foreach}
loop. Unlike many parallel programming packages for R, \texttt{foreach}
doesn't require the body of the \texttt{for} loop to be turned into a
function. \texttt{foreach} differs from a \texttt{for} loop in that its
return is a list of values, whereas a \texttt{for} loop has no value and
uses side effects to convey its result. Because of this,
\texttt{foreach} loops have a few advantages over \texttt{for} loops
when the purpose of the loop is to create a data structure such as a
vector, list, or matrix: First, there is less code duplication, and
hence, less chance for an error because the initialization of the vector
or matrix is unnecessary. Second, a \texttt{foreach} loop may be easily
parallelized by changing only a single keyword.
So what should you do if you have a nested \texttt{for} loop? Many
compute intensive algorithms use nested \texttt{for} loops, so this is
an important question, and there are some features in the
\texttt{foreach} package that help with this issue.
Let's say that we want to perform a Monte Carlo simulation, using a
function called \texttt{sim}. The \texttt{sim} function takes two
arguments, and we want to call it with all combinations of values that
are stored in the vectors \texttt{avec} and \texttt{bvec}. The
following doubly-nested \texttt{for} loop does that. For testing
purposes, the \texttt{sim} function is defined to return $10 a + b$:
<<init1,echo=FALSE,results=hide>>=
sim <- function(a, b) 10 * a + b
avec <- 1:2
bvec <- 1:4
@
\begin{example}[h]
<<for1>>=
x <- matrix(0, length(avec), length(bvec))
for (j in 1:length(bvec)) {
for (i in 1:length(avec)) {
x[i,j] <- sim(avec[i], bvec[j])
}
}
x
@
\caption{Doubly-nested for loop}
\end{example}
In this case, it makes sense to store the results in a matrix, so we
create one called \texttt{x}, and assign the return value of
\texttt{sim} into the appropriate element of \texttt{x} each time
through the inner loop.
When converting this to use \texttt{foreach}, we avoid the use of
assignments, and instead return an appropriate object from
\texttt{foreach}.\footnote{Actually, it is the \texttt{\%do\%} or
\texttt{\%dopar\%} operator that is returning the value, but it's less
confusing and more natural to pretend that \texttt{foreach} is returning
the value, especially since the \texttt{foreach} options control the
value that is returned from \texttt{\%do\%} and \texttt{\%dopar\%}.} By
default, \texttt{foreach} returns the values in a list, but we can
change that with the \texttt{.combine} option. The natural solution is
to have the inner \texttt{foreach} return a vector using the standard
\texttt{c} function, and the outer \texttt{foreach} return a matrix by
concatenating those vectors using the \texttt{cbind} function. Here's
one way to do that:\footnote{Note my use of braces. They aren't
actually necessary for the inner \texttt{foreach} loop, but they needed
for the outer \texttt{foreach} loop to get the correct operator
precedence. \texttt{\%do\%} is a binary operator whose second argument
(or right-hand argument) is an R expression, and the R expression that
I'm trying to execute is the entire inner \texttt{foreach} loop. You
can also use parentheses to get the correct precedence, but braces look
more natural since \texttt{foreach} pretends to be a \texttt{for} loop.}
\begin{example}[h]
<<foreach1>>=
x <-
foreach(b=bvec, .combine='cbind') %do% {
foreach(a=avec, .combine='c') %do% {
sim(a, b)
}
}
x
@
\caption{Doubly-nested foreach loop}
\end{example}
This is actually cleaner than the original \texttt{for} loop version,
since \texttt{foreach} is intended to return a value, as we're doing in
this example.
To execute this in parallel, all we have to do is change the outer
\texttt{\%do\%} into a \texttt{\%dopar\%}:\footnote{I also had to use
the \texttt{.packages} option to specify that the \texttt{foreach}
package was needed to execute the body of the \texttt{foreach}.}
\begin{example}[h]
<<foreach2>>=
x <-
foreach(b=bvec, .combine='cbind', .packages='foreach') %dopar% {
foreach(a=avec, .combine='c') %do% {
sim(a, b)
}
}
x
@
\caption{Parallel doubly-nested foreach loop}
\end{example}
\section{Task Granularity}
An experienced R programmer will very likely ask why the inner
\texttt{foreach} loop is used in my last example. Wouldn't it be more
efficient to use the \texttt{sapply} function in its place?
\begin{example}[h]
<<foreach3>>=
x <-
foreach(b=bvec, .combine='cbind') %dopar% {
sapply(avec, sim, b)
}
x
@
\caption{Parallel singly-nested foreach loop}
\label{e:sapply}
\end{example}
My answer would be yes, I would use \texttt{sapply}, as in example
\ref{e:sapply}. In this case, the \texttt{sapply} makes a very simple
replacement for the inner \texttt{foreach} loop, and will certainly run
somewhat faster. But it's possible that neither example is ideal. The
reason for that revolves around the concept of task granularity.
Parallel computing is all about executing tasks in parallel.
With \texttt{foreach}, the task is the body of the loop, and so in this
case, the task is the execution of the \texttt{sim} function. If each
task takes a relatively short time to execute, we say that
we have fine grained tasks. If each task takes a long time to
execute, we say that we have coarse grained tasks. Generally speaking,
coarse grained tasks are better, since they are easier to execute
efficiently. If a task takes less than a second to execute, you can
run into problems executing efficiently with \texttt{foreach}, which was
designed for coarse grained parallelism.
Of course, if the body of your \texttt{for} loop executes in a
millisecond, why would you want to run the loop in parallel? The
obvious answer is that you have a lot of millisecond tasks to execute.
A hundred thousand such tasks will take at least 100 seconds to
execute, and that's a problem that you might want to execute
in parallel.
\section{Which loop to parallelize?}
Now let's return to our example. If the \texttt{sim} function executes
in a millisecond, then we wouldn't want to execute it in parallel. But
the body of example three is using \texttt{sapply} to execute
\texttt{sim} multiple times. If the length of \texttt{avec} is 10
million, and the length of \texttt{bvec} is a thousand, then we're in
great shape. It would take around two hours to run this sequentially,
but we can probably run this efficiently on a two core machine, or
a two hundred node cluster. That's because we have a thousand
reasonably sized tasks to execute.
Unfortunately, life isn't always easy, especially when computers are
involved. If the \texttt{avec} vector is short, the tasks could still be
too fine grained, making them run inefficiently. And if \texttt{bvec}
is much smaller than \texttt{avec}, you could have only a few very
long running tasks, limiting the parallelism of the job.
This seems like a rather bleak situation: even though you have a lot of
completely independent tasks and lots of CPU's to execute them on,
unless you're lucky and get a good balance between the number and size
of the tasks, you could still end up waiting a long time for the work to
get done.
\section{Singly-nested loops}
Nested \texttt{for} loops seem like a great opportunity for parallelism,
because where you have nested loops, you often have many tasks that can
be executed in parallel. But as we've seen, the nesting can make it
more difficult to get a good balance between the size and number of the
tasks. With a single \texttt{for} loop, you can still have this kind of
balance problem, of course. The standard problem is that the tasks are
too fine grained, and the standard solution is to batch many tasks
together into large tasks. I call this task chunking, because you put
multiple tasks together into a larger task chunk.
The \texttt{doNWS} parallel backend has a \texttt{chunkSize} option that
allows you to specify the number of tasks to batch together. It's a
backend specific option, so it is specified via the \texttt{foreach}
\texttt{.options.nws} option. Here's how it can be used in a
singly-nested \texttt{foreach} loop:
\begin{example}[h]
<<foreach4>>=
nwsopts <- list(chunkSize=100)
x <-
foreach(a=avec, .combine='c', .options.nws=nwsopts) %dopar%
sim(a, 1)
x
@
\caption{Singly-nested for loop}
\end{example}
The other problem that you could run into is that the tasks are too
big compared to the number of tasks. But the only solution to that
problem is to attempt decompose the task further into subtasks. If
that's your problem, then all I can say is "Good Luck".
\section{The \texttt{\%:\%} Operator}
Now let's return to nested loops. One solution to our problem is to
convert the nested loops into a single loop of tasks that can all be
executed in parallel. After all, that's what's frustrating about
example one: all the tasks are completely independent, so we should be
able to execute them in parallel. But the nested loops don't express
that. The inner loop has to completely finish before you can go on to
the next iteration of the outer loop. But that isn't necessary in this
case. If we had a way to write a nested loop that represented a single
stream of tasks, then we wouldn't have to worry about problems caused by
an imbalance between the number of iterations between the different
loops.
This is what the \texttt{foreach} nesting operator, \texttt{\%:\%}, was
designed for. It allows you to create one stream of tasks from multiple
\texttt{foreach} loops:\footnote{Note that \texttt{.option.nws} can
be specified either in the outer or inner \texttt{foreach}, but it
shouldn't be used in both.}
\begin{example}[h]
<<foreach6>>=
x <-
foreach(b=bvec, .combine='cbind', .options.nws=nwsopts) %:%
foreach(a=avec, .combine='c') %dopar%
sim(a, b)
x
@
\caption{The \%:\% operator}
\end{example}
Note that there is now only one \texttt{\%dopar\%} operator, not two.
There's no question of which loop to parallelize, because it's really
only one loop that's pretending to be two. Or rather, it's one loop
that still gives you the ability to handle the tasks and results in a
multidimensional way, so you can still use different combine function for
the different loops, for example.
\section{A more complicated example}
[todo]
\section{Conclusion}
Nested \texttt{for} loops are a common construct, and are often
the most time consuming part of R scripts, so they are a prime candidate
for parallelization. The standard advice is to parallelize the outer
loop, and vectorize the inner loop. But as we've seen, that often leads
to suboptimal performance due to an imbalance between the size and
number of tasks that need to be executed. By using the \texttt{\%:\%}
operator with \texttt{foreach}, and by using chunking techniques, many
of those problems can be overcome. The resulting code is often clearer
and more readable than the original R code, since \texttt{foreach} was
designed to deal with exactly this kind of problem.
\end{document}
|