1.1 --- a/docs/tutorial.html Mon Jun 02 22:43:11 2008 +0000
1.2 +++ b/docs/tutorial.html Mon Jun 02 22:43:43 2008 +0000
1.3 @@ -15,7 +15,18 @@
1.4 advantage of more than one processor to simultaneously process data - is to
1.5 use the <code>pmap</code> function.</p>
1.6
1.7 -<h2>Converting Map-Style Code</h2>
1.8 +<ul>
1.9 +<li><a href="#pmap">Converting Map-Style Code</a></li>
1.10 +<li><a href="#Map">Converting Invocations to Parallel Operations</a></li>
1.11 +<li><a href="#Queue">Converting Arbitrarily-Ordered Invocations</a></li>
1.12 +<li><a href="#create">Converting Inline Computations</a></li>
1.13 +<li><a href="#MakeReusable">Reusing Processes in Parallel Programs</a></li>
1.14 +<li><a href="#BackgroundCallable">Performing Computations in Background Processes</a></li>
1.15 +<li><a href="#ManagingBackgroundProcesses">Managing Several Background Processes</a></li>
1.16 +<li><a href="#Summary">Summary</a></li>
1.17 +</ul>
1.18 +
1.19 +<h2 id="pmap">Converting Map-Style Code</h2>
1.20
1.21 <p>Consider a program using the built-in <code>map</code> function and a sequence of inputs:</p>
1.22
1.23 @@ -99,7 +110,7 @@
1.24 variable is defined elsewhere), several calculations can now be performed in
1.25 parallel.</p>
1.26
1.27 -<h2>Converting Invocations to Parallel Operations</h2>
1.28 +<h2 id="Map">Converting Invocations to Parallel Operations</h2>
1.29
1.30 <p>Although some programs make natural use of the <code>map</code> function,
1.31 others may employ an invocation in a nested loop. This may also be converted
1.32 @@ -198,7 +209,7 @@
1.33 list operations provides the results in the same order as their corresponding
1.34 inputs.</p>
1.35
1.36 -<h2>Converting Arbitrarily-Ordered Invocations</h2>
1.37 +<h2 id="Queue">Converting Arbitrarily-Ordered Invocations</h2>
1.38
1.39 <p>In some programs, it is not important to receive the results of
1.40 computations in any particular order, usually because either the order of
1.41 @@ -245,7 +256,7 @@
1.42 <p>We can bring the benefits of parallel processing to the above program with
1.43 the following code:</p>
1.44
1.45 -<pre>
1.46 +<pre id="simple_managed_queue">
1.47 t = time.time()
1.48
1.49 # Initialise the communications queue with a limit on the number of
1.50 @@ -433,7 +444,7 @@
1.51 <code>start</code> method still calls the provided callable, but using a
1.52 different notation from that employed previously.</p>
1.53
1.54 -<h2>Converting Inline Computations</h2>
1.55 +<h2 id="create">Converting Inline Computations</h2>
1.56
1.57 <p>Although many programs employ functions and other useful abstractions which
1.58 can be treated as parallelisable units, some programs perform computations
1.59 @@ -525,7 +536,7 @@
1.60 the results in the same order as the initiation of the computations which
1.61 produced them.</p>
1.62
1.63 -<h2>Reusing Processes in Parallel Programs</h2>
1.64 +<h2 id="MakeReusable">Reusing Processes in Parallel Programs</h2>
1.65
1.66 <p>One notable aspect of the above programs when parallelised is that each
1.67 invocation of a computation in parallel creates a new process in which the
1.68 @@ -577,7 +588,273 @@
1.69 creating a new process for each computation and then discarding it, only to
1.70 create a new process for the next computation.</p>
1.71
1.72 -<h2>Summary</h2>
1.73 +<h2 id="BackgroundCallable">Performing Computations in Background Processes</h2>
1.74 +
1.75 +<p>Occasionally, it is desirable to initiate time-consuming computations and to
1.76 +not only leave such processes running in the background, but to be able to detach
1.77 +the creating process from them completely, potentially terminating the creating
1.78 +process altogether, and yet also be able to collect the results of the created
1.79 +processes at a later time, potentially in another completely different process.
1.80 +For such situations, we can make use of the <code>BackgroundCallable</code>
1.81 +class, which converts a parallel-aware callable into a callable which will run
1.82 +in a background process when invoked.</p>
1.83 +
1.84 +<p>Consider this excerpt from a modified version of the <a
1.85 +href="#simple_managed_queue">simple_managed_queue</a> program:</p>
1.86 +
1.87 +<pre>
1.88 +<strong>def task():</strong>
1.89 +
1.90 + # Initialise the communications queue with a limit on the number of
1.91 + # channels/processes.
1.92 +
1.93 + queue = pprocess.Queue(limit=limit)
1.94 +
1.95 + # Initialise an array.
1.96 +
1.97 + results = [0] * N * N
1.98 +
1.99 + # Wrap the calculate function and manage it.
1.100 +
1.101 + calc = queue.manage(pprocess.MakeParallel(calculate))
1.102 +
1.103 + # Perform the work.
1.104 +
1.105 + print "Calculating..."
1.106 + for i in range(0, N):
1.107 + for j in range(0, N):
1.108 + calc(i, j)
1.109 +
1.110 + # Store the results as they arrive.
1.111 +
1.112 + print "Finishing..."
1.113 + for i, j, result in queue:
1.114 + results[i*N+j] = result
1.115 +
1.116 + <strong>return results</strong>
1.117 +</pre>
1.118 +
1.119 +<p>Here, we have converted the main program into a function, and instead of
1.120 +printing out the results, we return the results list from the function.</p>
1.121 +
1.122 +<p>Now, let us consider the new main program (with the relevant mechanisms
1.123 +highlighted):</p>
1.124 +
1.125 +<pre>
1.126 + t = time.time()
1.127 +
1.128 + if "--reconnect" not in sys.argv:
1.129 +
1.130 + # Wrap the computation and manage it.
1.131 +
1.132 + <strong>ptask = pprocess.BackgroundCallable("task.socket", pprocess.MakeParallel(task))</strong>
1.133 +
1.134 + # Perform the work.
1.135 +
1.136 + ptask()
1.137 +
1.138 + # Discard the callable.
1.139 +
1.140 + del ptask
1.141 + print "Discarded the callable."
1.142 +
1.143 + if "--start" not in sys.argv:
1.144 +
1.145 + # Open a queue and reconnect to the task.
1.146 +
1.147 + print "Opening a queue."
1.148 + <strong>queue = pprocess.BackgroundQueue("task.socket")</strong>
1.149 +
1.150 + # Wait for the results.
1.151 +
1.152 + print "Waiting for persistent results"
1.153 + for results in queue:
1.154 + pass # should only be one element
1.155 +
1.156 + # Show the results.
1.157 +
1.158 + for i in range(0, N):
1.159 + for result in results[i*N:i*N+N]:
1.160 + print result,
1.161 + print
1.162 +
1.163 + print "Time taken:", time.time() - t
1.164 +</pre>
1.165 +
1.166 +<p>(This code in context with <code>import</code> statements and functions is
1.167 +found in the <code>examples/simple_background_queue.py</code> file.)</p>
1.168 +
1.169 +<p>This new main program has two parts: the part which initiates the
1.170 +computation, and the part which connects to the computation in order to collect
1.171 +the results. Both parts can be run in the same process, and this should result
1.172 +in similar behaviour to that of the original
1.173 +<a href="#simple_managed_queue">simple_managed_queue</a> program.</p>
1.174 +
1.175 +<p>In the above program, however, we are free to specify <code>--start</code> as
1.176 +an option when running the program, and the result of this is merely to initiate
1.177 +the computation in a background process, using <code>BackgroundCallable</code>
1.178 +to obtain a callable which, when invoked, creates the background process and
1.179 +runs the computation. After doing this, the program will then exit, but it will
1.180 +leave the computation running as a collection of background processes, and a
1.181 +special file called <code>task.socket</code> will exist in the current working
1.182 +directory.</p>
1.183 +
1.184 +<p>When the above program is run using the <code>--reconnect</code> option, an
1.185 +attempt will be made to reconnect to the background processes already created by
1.186 +attempting to contact them using the previously created <code>task.socket</code>
1.187 +special file (which is, in fact, a UNIX-domain socket); this being done using
1.188 +the <code>BackgroundQueue</code> function which will handle the incoming results
1.189 +in a fashion similar to that of a <code>Queue</code> object. Since only one
1.190 +result is returned by the computation (as defined by the <code>return</code>
1.191 +statement in the <code>task</code> function), we need only expect one element to
1.192 +be collected by the queue: a list containing all of the results produced in the
1.193 +computation.</p>
1.194 +
1.195 +<h2 id="ManagingBackgroundProcesses">Managing Several Background Processes</h2>
1.196 +
1.197 +<p>In the above example, a single background process was used to manage a number
1.198 +of other processes, with all of them running in the background. However, it can
1.199 +be desirable to manage more than one background process.</p>
1.200 +
1.201 +<p>Consider this excerpt from a modified version of the <a
1.202 +href="#simple_managed_queue">simple_managed_queue</a> program:</p>
1.203 +
1.204 +<pre>
1.205 +<strong>def task(i):</strong>
1.206 +
1.207 + # Initialise the communications queue with a limit on the number of
1.208 + # channels/processes.
1.209 +
1.210 + queue = pprocess.Queue(limit=limit)
1.211 +
1.212 + # Initialise an array.
1.213 +
1.214 + results = [0] * N
1.215 +
1.216 + # Wrap the calculate function and manage it.
1.217 +
1.218 + calc = queue.manage(pprocess.MakeParallel(calculate))
1.219 +
1.220 + # Perform the work.
1.221 +
1.222 + print "Calculating..."
1.223 + <strong>for j in range(0, N):</strong>
1.224 + <strong>calc(i, j)</strong>
1.225 +
1.226 + # Store the results as they arrive.
1.227 +
1.228 + print "Finishing..."
1.229 + <strong>for i, j, result in queue:</strong>
1.230 + <strong>results[j] = result</strong>
1.231 +
1.232 + <strong>return i, results</strong>
1.233 +</pre>
1.234 +
1.235 +<p>Just as we see in the previous example, a function called <code>task</code>
1.236 +has been defined to hold a background computation, and this function returns a
1.237 +portion of the results. However, unlike the previous example or the original
1.238 +example, the scope of the results of the computation collected in the function
1.239 +have been changed: here, only results for calculations involving a certain value
1.240 +of <code>i</code> are collected, with the particular value of <code>i</code>
1.241 +returned along with the appropriate portion of the results.</p>
1.242 +
1.243 +<p>Now, let us consider the new main program (with the relevant mechanisms
1.244 +highlighted):</p>
1.245 +
1.246 +<pre>
1.247 + t = time.time()
1.248 +
1.249 + if "--reconnect" not in sys.argv:
1.250 +
1.251 + # Wrap the computation and manage it.
1.252 +
1.253 + <strong>ptask = pprocess.MakeParallel(task)</strong>
1.254 +
1.255 + <strong>for i in range(0, N):</strong>
1.256 +
1.257 + # Make a distinct callable for each part of the computation.
1.258 +
1.259 + <strong>ptask_i = pprocess.BackgroundCallable("task-%d.socket" % i, ptask)</strong>
1.260 +
1.261 + # Perform the work.
1.262 +
1.263 + <strong>ptask_i(i)</strong>
1.264 +
1.265 + # Discard the callable.
1.266 +
1.267 + del ptask
1.268 + print "Discarded the callable."
1.269 +
1.270 + if "--start" not in sys.argv:
1.271 +
1.272 + # Open a queue and reconnect to the task.
1.273 +
1.274 + print "Opening a queue."
1.275 + <strong>queue = pprocess.PersistentQueue()</strong>
1.276 + <strong>for i in range(0, N):</strong>
1.277 + <strong>queue.connect("task-%d.socket" % i)</strong>
1.278 +
1.279 + # Initialise an array.
1.280 +
1.281 + <strong>results = [0] * N</strong>
1.282 +
1.283 + # Wait for the results.
1.284 +
1.285 + print "Waiting for persistent results"
1.286 + <strong>for i, result in queue:</strong>
1.287 + <strong>results[i] = result</strong>
1.288 +
1.289 + # Show the results.
1.290 +
1.291 + for i in range(0, N):
1.292 + <strong>for result in results[i]:</strong>
1.293 + print result,
1.294 + print
1.295 +
1.296 + print "Time taken:", time.time() - t
1.297 +</pre>
1.298 +
1.299 +<p>(This code in context with <code>import</code> statements and functions is
1.300 +found in the <code>examples/simple_persistent_queue.py</code> file.)</p>
1.301 +
1.302 +<p>In the first section, the process of making a parallel-aware callable is as
1.303 +expected, but instead of then invoking a single background version of such a
1.304 +callable, as in the previous example, we create a version for each value of
1.305 +<code>i</code> (using <code>BackgroundCallable</code>) and then invoke each one.
1.306 +The result of this is a total of <code>N</code> background processes, each
1.307 +running an invocation of the <code>task</code> function with a distinct value of
1.308 +<code>i</code> (which in turn perform computations), and each employing a
1.309 +UNIX-domain socket for communication with a name of the form
1.310 +<code>task-<em>i</em>.socket</code>.</p>
1.311 +
1.312 +<p>In the second section, since we now have more than one background process, we
1.313 +must find a way to monitor them after reconnecting to them; to achieve this, a
1.314 +<code>PersistentQueue</code> is created, which acts like a regular
1.315 +<code>Queue</code> object but is instead focused on handling persistent
1.316 +communications. Upon connecting the queue to each of the previously created
1.317 +UNIX-domain sockets, the queue acts like a regular <code>Queue</code> and
1.318 +exposes received results through an iterator. Here, the principal difference
1.319 +from previous examples is the structure of results: instead of collecting each
1.320 +individual value in a flat <code>i</code> by <code>j</code> array, a list is
1.321 +returned for each value of <code>i</code> and is stored directly in another
1.322 +list.</p>
1.323 +
1.324 +<h3>Applications of Background Computations</h3>
1.325 +
1.326 +<p>Background computations are useful because they provide flexibility in the
1.327 +way the results can be collected. One area in which they can be useful is Web
1.328 +programming, where a process handling an incoming HTTP request may need to
1.329 +initiate a computation but then immediately send output to the Web client - such
1.330 +as a page indicating that the computation is "in progress" - without having to
1.331 +wait for the computation or to allocate resources to monitor it. Moreover, in
1.332 +some Web architectures, notably those employing the Common Gateway Interface
1.333 +(CGI), it is necessary for a process handling an incoming request to terminate
1.334 +before its output will be sent to clients. By using a
1.335 +<code>BackgroundCallable</code>, a Web server process can initiate a
1.336 +communication, and then subsequent server processes can be used to reconnect to
1.337 +the background computation and to wait efficiently for results.</p>
1.338 +
1.339 +<h2 id="Summary">Summary</h2>
1.340
1.341 <p>The following table indicates the features used in converting one
1.342 sequential example program to another parallel program:</p>
1.343 @@ -602,7 +879,7 @@
1.344 <td>MakeParallel, Map, manage</td>
1.345 </tr>
1.346 <tr>
1.347 - <td rowspan="3">simple2</td>
1.348 + <td rowspan="5">simple2</td>
1.349 <td>simple_managed_queue</td>
1.350 <td>MakeParallel, Queue, manage</td>
1.351 </tr>
1.352 @@ -615,6 +892,14 @@
1.353 <td>Channel, Exchange (subclass), start, finish</td>
1.354 </tr>
1.355 <tr>
1.356 + <td>simple_background_queue</td>
1.357 + <td>MakeParallel, BackgroundCallable, BackgroundQueue</td>
1.358 + </tr>
1.359 + <tr>
1.360 + <td>simple_persistent_queue</td>
1.361 + <td>MakeParallel, BackgroundCallable, PersistentQueue</td>
1.362 + </tr>
1.363 + <tr>
1.364 <td>simple</td>
1.365 <td>simple_create_map</td>
1.366 <td>Channel, Map, create, exit</td>