* gstacksize removed.
[scilab.git] / scilab / modules / parallel / help / en_US / parallel_run.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!--
3  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
4  * Copyright (C) 2010 - DIGITEO Bernard HUGUENEY
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
11  *
12  -->
13 <refentry xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg" xmlns:ns3="http://www.w3.org/1999/xhtml" xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:db="http://docbook.org/ns/docbook" xmlns:scilab="http://www.scilab.org" xml:id="parallel_run" xml:lang="en">
14     <refnamediv>
15         <refname>parallel_run</refname>
16         <refpurpose>parallel calls to a function</refpurpose>
17     </refnamediv>
18     <refsynopsisdiv>
19         <title>Calling Sequence</title>
20         <synopsis>[R1[,…,Rm]]=parallel_run(A1[,…,Ak], f[,types][,dims], p)</synopsis>
21     </refsynopsisdiv>
22     <refsection>
23         <title>Arguments</title>
24         <variablelist>
25             <varlistentry>
26                 <term>Ai</term>
27                 <listitem>
28                     <para>Argument matrix of scalars (real) for function f. The Ai
29                         matrices do not need to have the same number of columns. Let n be
30                         the largest number of columns for the Ai : f will be called <literal>n</literal> times
31                         with arguments Ai(:,k) for k=1…n. For Ai with less than n
32                         columns, the columns are 'recycled' and the kth call is passed
33                         Ai(:,(modulo(k-1,n)+1).
34                     </para>
35                 </listitem>
36             </varlistentry>
37             <varlistentry>
38                 <term>f</term>
39                 <listitem>
40                     <para>A scilab macro or a string containing the name of the function to call.</para>
41                 </listitem>
42             </varlistentry>
43             <varlistentry>
44                 <term>types</term>
45                 <listitem>
46                     <para>
47                         strings containing the type name (as per <link linkend="typeof">typeof</link>) of results of the function <emphasis role="bold">f</emphasis>. Currently, only "constant" for reals
48                         (scalar or matrix) is supported. Defaults to "constant".
49                     </para>
50                 </listitem>
51             </varlistentry>
52             <varlistentry>
53                 <term>dims</term>
54                 <listitem>
55                     <para>matrix with 1 or 2 columns containing the dimensions of the
56                         results of the function <emphasis role="bold">f</emphasis>. Defaults
57                         to 1 (i.e. scalar results).
58                     </para>
59                 </listitem>
60             </varlistentry>
61             <varlistentry>
62                 <term>p</term>
63                 <listitem>
64                     <para>
65                         parameters list (as created with <link linkend="init_param">init_param</link>) used for tuning the
66                         parallelization strategy. See  <emphasis role="bold">§Tuning the Parallelization with Configuration Parameters</emphasis> for the various parameters that can be set.
67                     </para>
68                 </listitem>
69             </varlistentry>
70             <varlistentry>
71                 <term>Ri</term>
72                 <listitem>
73                     <para>vectors of n (see above) columns containing the results of the
74                         function called. The number of rows are given by the corresponding
75                         size argument if any (1 otherwise).
76                     </para>
77                 </listitem>
78             </varlistentry>
79         </variablelist>
80     </refsection>
81     <refsection>
82         <title>Description</title>
83         <para>parallel_run makes parallel calls (on a multicore system) to the
84             provided function on the supplied vectors of arguments. The function can
85             be the name of either a compiled foreign function (see <link linkend="ilib_for_link">ilib_for_link</link>) or a Scilab macro. In the
86             latter case, the macro should not rely on side effects because some of them
87             will be lost (those performed in other processes than the main scilab
88             process).  The number of
89             calls (and dimension of the result vectors) is given by the length of the
90             longest vector of arguments.
91         </para>
92         <para>Except for side effects, [R1[,…,Rm]]=parallel_run(A1[,…,Ak],
93             'f'[,types][,dims][,C]) is equivalent to the Scilab pseudo code:
94         </para>
95         <programlisting role="example"><![CDATA[for i = 1:max(size(A1,'c'),…,size(Ak,'c'))
96   [R1(:,i),…,Rm(:,i)]= f(A1(:(modulo(i-1,size(A1,'c'))+1),…,Ak(:(modulo(i-1,size(Ak,'c'))+1));
97 end; ]]></programlisting>
98         <para>
99             Note that the equivalent code does not need <literal>types</literal>, <literal>dims</literal> or <literal>p</literal>.
100             <literal>parallel_run</literal> needs them for performance reasons : <literal>types</literal> and <literal>dims</literal> are used
101             to pre-allocate the result variables and  <literal>p</literal> is used to fine tune the
102             parallelization strategy.
103         </para>
104     </refsection>
105     <refsection>
106         <title>Limitations</title>
107         <para>
108             In this current version of Scilab, <literal>parallel_run</literal> uses only one core on Windows platforms.
109         </para>
110         <para>In order to provide the most efficient implementation possible, the least that parallel_run can do is to avoid imposing an overhead over an explicit loop. Unfortunately, this can only be achieved on some data structures.
111             For matrices of scalar, extracting a single column to pass as argument to the function can be done very efficiently.
112             Not so for matrices of strings or sparse matrices (not to mention lists !).
113             For now, parallel_run only handles arguments and results of scalar  matrices of real values and the <emphasis role="bold">types</emphasis> argument is not used.
114         </para>
115         <para>Furthermore, no locking primitives are available in Scilab to handle concurrent accesses to shared variables.
116             For this reason, the concurrent execution of Scilab macros cannot be safe in a shared memory environment and each parallel execution must be done in a separate memory space. As a result, one should not rely on side effects such as modifying variables from outer scope : only the data stored into the result  variables will be copied back into the calling environment.
117         </para>
118         <para>
119             As it is not possible to synchronize the access to Java Virtual Machine accross separate memory spaces, Scilab macros called by parallel_run are not allowed to use the JVM. This unfortunately encompass all the graphics functions as the Scilab GUI is based on Java. The spawned Scilab process are run in "nwni" mode their calls to <link linkend="disp">disp()</link> are redirected to the standard output.
120         </para>
121         <para>
122             A last limitation imposed by the aim to minimize data shuffling info memory is that no stack resizing (via <link linkend="stacksize">stacksize()</link>) should take place during a call to <literal>parallel_run</literal> (neither by the function f nor by the prologue or epilogue cf. infra.). Attempts to do do will result in a warning and leave the stack untouched.
123         </para>
124     </refsection>
125     <refsection>
126         <title>Advanced usage</title>
127         <para>
128             To achieve maximum performance and void any unwarranted copy and heavy context switching, it is possible to use parallel_run on compiled foreign functions. Such and advanced usage of parallel_run is detailed bellow in <emphasis role="bold">§ Advanced Usage : Compiled Foreign Functions</emphasis>.
129         </para>
130     </refsection>
131     <refsection>
132         <title>Examples </title>
133         <refsection>
134             <title>First steps </title><para>First we rewrite the most trivial loop with parallel_run:</para><programlisting role="example"><![CDATA[for i=1:10
135   res(i)= i*i;
136 end;
137 // for parallel_run, we need to have a function performing the computation
138 function a= g(arg1)
139   a=arg1*arg1
140 endfunction
141
142 res=parallel_run(1:10, g); // res = [1., 4., 9., 16., 25., 36., 49., 64., 81., 100.]; ]]></programlisting>
143             The matrix res was filled with res(i)= g(i), with concurrent calls to g(). To see that the calls to <literal>g</literal> are not sequential, we can add some output to the function :
144             <programlisting role="example"><![CDATA[function a= g(arg1)
145 disp("called on "+string(arg1));
146   a=arg1*arg1;
147 disp("computed "+string(a));
148 endfunction
149
150 res=parallel_run(1:6, g); // the actual output depends on the architecture (i.e. 4 cores) and current load
151  called on 3
152
153  computed 9
154
155  called on 2
156
157  called on 5
158
159  computed 25
160
161
162  computed 4
163
164  called on 1
165  called on 6
166
167  computed 36
168
169  computed 1
170
171  called on 4
172
173  computed 16]]></programlisting>
174             As we can see, not only were the calls to <literal>g</literal> 'out of order', but they where also interleaved.
175             <para>
176                 <emphasis role="bold">Note :</emphasis> This example is for illustrative purposes only ! When the operations performed by the function are available in vector form, it is much more efficient to use these operators than to call parallel_run. For instance, the most efficient way to perform g() on a vector of values would be to rewrite it as <emphasis role="italic"> a= arg1 .* arg1</emphasis> an call it directly on the matrix of arguments (i.e res= g(1:10);). parallel_run is intended for more involved functions.
177             </para>
178             To illustrate the running time gain, we instrument the next example with calls to <link linkend="getdate">getdate()</link>. However, the next examples will focus only on the illustrative purpose irrespectively of any actual gain. Running the simplest Scilab function with a relevant signature on datasets small enough to be printed is unlikely to benefit from multicore parallel scheduling ! Each example will perform a computation with an explicit loop, and then the same computation with parallel_run.
179             <title>Wallclock gain</title>
180             <programlisting role="example"><![CDATA[function r=f(n)
181    if(n == 0)
182      r= 1;
183    else
184      r= n-m(f(n-1));
185    end;
186 endfunction
187
188 function r=m(n)
189    if(n == 0)
190      r= 0;
191    else
192      r= n - f(m(n-1));
193    end;
194 endfunction;
195
196 n_max=40;
197
198 t0=getdate();
199 for i=1:n_max
200    r(i)= m(i);
201 end;
202 etime(getdate(), t0) // output the wallclock time for the explicit loop computation
203
204 t0=getdate();
205 r= parallel_run(1:n_max,"m");
206 // output the wallclock time for the parallel_run computation,
207 // it should be lower that the previous on multicore architectures.
208 etime(getdate(), t0)]]></programlisting>
209             <title>Plurality of arguments and returned values</title>
210             It is of course possible to have more than one argument and more than one returned value :
211             <programlisting role="example"><![CDATA[function [r_min, r_med, r_max]= min_med_max(a, b, c)
212   r_min=min(a,b,c); r_med=median([a,b,c]); r_max=max(a,b,c);
213 endfunction
214
215 N=10;
216 A=rand(1:N);B=rand(1:N);C=rand(1:N);
217
218 Min=zeros(N); Med=Min; Max=Min;
219 for i =1:N
220   [Min(i), Med(i), Max(i)]= min_med_max(A(i), B(i), C(i));
221 end;
222
223 [Min,Med,Max]=parallel_run(A,B,C,"min_med_max");// equivalent to the previous loop.]]></programlisting>
224             
225             
226             As we have seen in the Note of §First steps, the best performance is achieved by making use of vector (1xn matrices) operations. With parallel_run, it is also possible to take advantage of those operations by taking vector arguments and returning vector results. When returning a vector, one must use the  <literal>dims</literal> so that  <literal>parallel_run</literal> knows beforehand the number of returned values to expect.
227             <title>Vector arguments and results</title>
228             <programlisting role="example"><![CDATA[function r= quantiles(data, ranks)
229   tmp=gsort(data,'g','i');
230   r=tmp(ranks);
231 endfunction
232
233 N=100;
234 data=matrix(rand(1:N*10), N, 10);
235
236 R4_6_7=matrix(zeros(1:3*10),3,10);
237 for i =1:10
238   R4_6_7(:,i)= quantiles(data(:,i),[4;6;7]);
239 end;
240 // note the last argument to inform that quantiles returns 3 scalars.
241 R4_6_7=parallel_run(data,[4;6;7],"quantiles", 3);]]></programlisting>
242             While it is only possible to pass vectors (1 x n) as columns of argument and/or result matrices, it is easy to reshape any matrix into such vector to circumvent this restriction. The previous example was passing arguments with different numbers of columns. In fact, the second argument to pass to <literal>quantiles</literal> only has one columns, because all the calls to the function must be made with the same ranks. This column is recycled as if the second argument had as much columns as the first, all with the same values.
243             <para>
244                 <emphasis role="bold">Note :</emphasis> In the previous example, the shape of the second argument is of utmost importance for parallel_run ! While it was possible to write 
245                 <emphasis role="italic">
246                     R4_6_7(:,i)= quantiles(data(:,i),[4<emphasis role="bold">,</emphasis>6<emphasis role="bold">,</emphasis>7]);
247                 </emphasis>
248                 (note the comma) in the explicit loop, calling <emphasis role="italic">parallel_run(data,[4;6;7],"quantiles", 3)</emphasis> would have computed a very different result :
249                 <programlisting role="example"><![CDATA[// with previous definitions
250 -->parallel_run(data,[4;6;7],"quantiles", 3)
251 ans  =
252
253 0.0000426  0.0000512  0.0000387  0.0000271  0.0000175  0.0000462  0.0000243  0.0000302  0.0000483  0.0000099
254 0.0000799  0.0000681  0.0000516  0.0000435  0.0000576  0.0000645  0.0000700  0.0000478  0.0000604  0.0000252
255 0.0000963  0.0000953  0.0000649  0.0000452  0.0000650  0.0000805  0.0000793  0.0000615  0.0000666  0.0000335
256 -->parallel_run(data,[4,6,7],"quantiles", 3)
257   ans  =
258
259 0.0000426  0.0000681  0.0000649  0.0000271  0.0000576  0.0000805  0.0000243  0.0000478  0.0000666  0.0000099
260 0.         0.         0.         0.         0.         0.         0.         0.         0.         0.
261 0.         0.         0.         0.         0.         0.         0.         0.         0.         0.]]></programlisting>
262                 The second call to <literal>parallel_run</literal> has a second argument of 3 columns and 1 row, so each call to <literal>quantiles</literal> only gets 1 scalar as second argument. The first call to  <literal>quantiles</literal> gets '4' and returns the 4th value of the first columns of data. This is confirmed by the fact that the returned value (0.0000426) is the same as the first returned by the previous call to  <literal>parallel_run</literal> (which returned the 4th, 6th and 7th values). For the second call to  <literal>parallel_run</literal>, the two other values of the first result column (here 0. ) are undefined because we stated that <literal>quantiles</literal> would return 3 values (the last argument to  <literal>parallel_run</literal> ) while in fact <literal>quantiles</literal> only returned one (because it only got one rank as second argument). The second call to <literal>quantiles</literal> gets '6' and returns the 6th value of the second columns of data. This is confirmed by the fact that the returned value (0.0000681) is the same as the first returned by the previous call to  <literal>parallel_run</literal> (which returned the 4th, 6th and 7th values). For the second call to  <literal>parallel_run</literal>, the two other values of the second result column (here 0. ) are undefined for the reason we have just seen. The third call to <literal>quantiles</literal> gets '7' and computes the 7th rank. For the next calls, the argument matrix with only three columns has its columns recycled so that  <literal>quantiles</literal> gets '4' and then '6' and then '7' and then '4' etc.
263                 
264             </para>
265             <title>Handling other data types</title>
266             
267             <refsection>
268                 <title>Handling other argument types</title>
269                 While it is not possible to pass or return most Scilab types with functions called by  <literal>parallel_run</literal>, this does not mean that one cannot interact with any data structure. The easiest is to read from any kind of data type: as the global environment is shared amongst the parallel execution context, one can just pass column numbers instead of columns for instance, and let the macro access the data.
270                 
271                 <refsection>
272                     <title>Handling a sparse matrix argument</title>
273                     The following code sample shows how to access to elements of a sparse matrix, to compute quantiles of those elements.
274                     
275                     <programlisting role="example"><![CDATA[N=5; // size of a block
276 L=10; // number of blocks
277
278
279 data=spzeros(N*L,N*L);
280 for k=1:L // init diagonal blocks of sparse matrix with random numbers
281   kk=(k-1)*N+1:k*N;
282   data(kk,kk)=matrix(rand(1:N*N),N,N);
283 end
284 // function to compute quantiles in blocks from the data sparse matrix
285 function r= quantiles_sparse(indices_i, indices_j, ranks)
286   tmp=gsort(full(data(indices_i, indices_j)),'g','i');
287   r=tmp(ranks);
288 endfunction
289 // compute quantiles of diagonal blocks
290 // with explicit loop
291 R3_6_9=matrix(zeros(1:3*L),3,L);
292 for k=1:L
293   kk=(k-1)*N+1:k*N;
294   R3_6_9(:,k)=quantiles_sparse(kk,kk, [3,6,9]);
295 end;
296 // compute quantiles of diagonal blocks
297 // with parallel_run
298 idx=matrix(1:(L*N), N,L);
299 R3_6_9=parallel_run(idx,idx,[3;6;9],"quantiles_sparse",3);]]></programlisting>
300                 </refsection>
301                 <refsection>
302                     <title>Handling a matrix of strings argument</title>
303                     The following code sample shows how to access to elements of a matrix of strings, to compute the number of digits in those strings..
304                     
305                     <programlisting role="example"><![CDATA[a=["a1";"b2b";"1cc2c";"4d555"]
306
307 function result=count_digits(str)
308   result=sum(isdigit(str));
309 endfunction
310
311 for i=1:size(a,'r')
312   nb_digits(:,i)=count_digits(a(i));
313 end;
314
315 function result=count_digits_i(index) // we must define another function taking an index argument
316   result=sum(isdigit(a(index))); // and accessing the string from the variable in outer scope
317 endfunction
318
319 nb_digits= parallel_run(1:size(a,'r'),"count_digits_i");]]></programlisting>
320                 </refsection>
321             </refsection>
322             
323         </refsection>
324         <refsection>
325             <title>Handling other result types</title>
326             Other result types are much harder to handle and should be avoid if at all possible. It would be useless to write to variables from outer scope because each execution environment has its own copy of the memory space. The only solution is to find a mapping to a vector of scalars and channel the result data through it.
327             
328             <refsection>
329                 <title>Handling a sparse matrix argument and result type</title>
330                 The following code sample shows how to access to elements of a sparse matrix, to compute the invert of a block diagonal sparse matrix, and return the result in a sparse matrix.
331                 
332                 <programlisting role="example"><![CDATA[N=5; // size of a block
333 L=10; // number of blocks
334
335
336 data=spzeros(N*L,N*L);
337 for k=1:L// init sparse matrix with random diagonal blocks
338   kk=(k-1)*N+1:k*N;
339   data(kk,kk)=matrix(rand(1:N*N),N,N);
340 end
341
342 // invert the diagonal blocks of the sparse matrix
343 // with explicit loop
344 r=data;
345 for k=1:L
346   kk=(k-1)*N+1:k*N;
347   r(kk,kk)=inv(data(kk,kk));
348 end;
349
350 // function to compute the invert of a block from the sparse matrix
351 // it cannot take a sparse matrix argument so it must take the block number (k)
352 // a retrieve the block from the variable data in outer scope
353 // it also cannot put the result directly in a sparse matrix result
354 // so we return the full matrix of the inverted block
355 function r= invert_one_block(block_size,k)
356   kk=(k-1)*block_size+1:k*block_size;
357   r=full(inv(data(kk,kk)));// data from global scope
358 endfunction;
359
360 // function to make a sparse matrix and fill it with the full results
361 // from parallel_run
362 function r=to_sparse(full_diag,block_size) // full_diag is L * block_size^2
363 s=size(full_diag);
364 nb_blocks=s(2);
365 r=data;//data from global scope
366 for k=1:s(2)
367   kk=(k-1)*block_size+1:k*block_size;
368   r(kk,kk)= matrix(full_diag(1:s(1),k),block_size,block_size);
369 end
370 endfunction
371
372 // call parallel_run() to operate on blocks of the sparse matrix
373 // and to_sparse() to build the sparse result
374 r=to_sparse(parallel_run(N,1:L,"invert_one_block",[N,N]),N);]]></programlisting>
375             </refsection>
376             <refsection>
377                 <title>Handling matrices of strings as argument and result type</title>
378                 The following uses the workaround seen in <emphasis role="bold">§Handling a matrix of strings argument</emphasis> to access a string matrix argument, and shows how to return strings by using their numeric representation with ascii(), to flip odd characters in strings.
379                 
380                 <programlisting role="example"><![CDATA[a=["one a";"b";"c and a word"];
381
382 // only works for ASCII
383 function p=censor_odds(str)
384 odds=(0:((length(str)-1)/2))*2+1;
385 p=ascii(str);
386 p(odds)= ascii('#');
387 p=ascii(p);
388 endfunction
389
390
391 for i=1:size(a,'r')
392   res(i)=censor_odds(a(i));
393 end;
394
395
396 function p=censor_odds_i(i,max_len) // pass an index and the max length
397 strlen=length(a(i));
398 odds=(0:((strlen-1)/2))*2+1;
399 p=ascii(a(i)+blanks(max_len-strlen));
400 p(odds)= ascii('#');
401 endfunction
402
403 function v=censor_v(codes)
404 for i=1:size(codes,'r')
405   v(i)=ascii(codes(i,:));
406 end;
407 endfunction
408
409 len=max(length(a));
410 res=censor_v(parallel_run(1:size(a,'r'), len, "censor_odds_i",len)');]]></programlisting>
411             </refsection>
412         </refsection>
413     </refsection>
414     <refsection>
415         <title>Advanced Usage : Compiled Foreign Functions</title>
416         When aiming at maximum performance, it sometimes makes sense to rewrite a few lines of Scilab code in a compiled language such as C++, C or Fortran.
417         <link linkend="ilib_for_link">ilib_for_link</link> allows to do it easily from Scilab, and <literal>parallel_run</literal> allows you to run your compiled code on your multicore architecture. Moreover, as it is possible to make compiled code thread-safe, it is also possible to launch compiled foreign function in lightweight (with less scheduling overhead) shared memory workers (threads), as will be seen in REF Parameters.
418         
419         <refsection>
420             <title>Compiled Foreign Functions API</title>
421             The restrictions imposed on the data types handled by <literal>parallel_run</literal>() allows to use a simpler API :
422             <programlisting role="example"><![CDATA[void f(void const* const* args, void *const* res)]]></programlisting>
423             Both arguments refer to data of opaque type <literal>(void*)</literal> and the implementation has to know which kind of data it receives and cast the pointers accordingly.
424             (Note that current implementation only supports double data, but integer types will soon be available.)
425             
426             <para>
427                 <emphasis role="bold">void const* const* args </emphasis> is a pointer to an array of the data of each argument Ai. If there is only one argument, then only args[0] is valid and contains the pointer to its data. Each argument  i can be a vector (it is a column of the Scilab matrix argument) and args[i-1] contains the pointer to its data. If argument i is a scalar (Scilab matrix Ai has only one row), only args[i][0] is valid and contains the value. Note : the data referenced by **args is declared const because it refers to arguments of the function. However, the data is currently copied from from underlying Scilab variables before being passed to the function, so it can safely be overwritten by casting away the constness(but conversely cannot be used as in/out parameters). If k arguments are provided to parallel_run, the args[0]...args[k-1] will always be valid for each call, even if some arguments had less columns than others : parallel_run will handle the "recycling" shown in <emphasis role="bold">§Description</emphasis>.
428             </para>
429             <para>
430                 <emphasis role="bold">void *const* res</emphasis> is a pointer to an array of the data of each  result Ri. If there is only one argument, then only res[0] is valid and contains the pointer to its data. Each result  i (counted from 0) can be a vector as specified by the dims argument and res[i] contains the pointer to its data. If res i is a scalar (default when no dims argument is used), only res[i][0] is valid and available to store the value.
431             </para>
432         </refsection>
433         
434         <refsection>
435             <title>Examples</title>
436             The following examples are the compiled foreign function rewrite of those from the previous section. However, we will not handle other data types because accessing those data structures from compiled code would be outside the scope of this document. Furthermore, when speed is of essence (as usual when one goes to the length of rewriting some Scilab code into a compiled language) one should strive to stick to dense matrices.
437             <refsection>
438                 <title>First steps </title><para>The most simple application of parallel_run is to</para><programlisting role="example"><![CDATA[function make_compiled_function(name, ext, code)
439   filename=name+ext;
440   mputl(code, filename);
441   ilib_for_link(fun_name,filename,[],"c");
442   exec loader.sce;
443   mdelete(filename);
444 endfunction
445
446 fun_name='test_fun';
447 c_prog=['#include  <math.h>'
448 'void '+fun_name+'(void const* const* args, void *const* res) {'
449 '*((double*)*res)= (((double*)args[0])[0])*(((double*)args[0])[0]);'
450 '}'];
451 make_compiled_function(fun_name, '.c',c_prog);
452
453 res=parallel_run(1:10, fun_name);// res = [1., 4., 9., 16., 25., 36., 49., 64., 81., 100.]; ]]></programlisting><title>Wallclock gain</title>
454                 In order to illustrate the parallelization gains, the next example includes an explicit loop calling a foreign c function with call.
455                 The remaining examples will only include the parallel_run version, as their purpose was already illustrated in the previous section.
456                 <programlisting role="example"><![CDATA[function make_compiled_function(name, ext, code)
457   filename=name+ext;
458   mputl(code, filename);
459   ilib_for_link(name,filename,[],"c");
460   exec loader.sce;
461   mdelete(filename);
462 endfunction
463
464 fun_name='male';
465 c_prog=['static int m(int);'
466 'static int f(int n) { return n ? (n-m(f(n-1))) : 1 ; }'
467 'static int m(int n) { return n ? (n-f(m(n-1))) : 0 ; }'
468 'void '+fun_name+'(double* res, double* n) '
469 '{ *res= (double)m((int)*n);}'];
470
471 make_compiled_function(fun_name, '.c',c_prog);
472
473 t0=getdate();for i=1:200
474   res_c(i)=call(fun_name, i,2,"d","out", [1,1],1,"d");
475 end;etime(getdate(), t0) // note that we ran the computation until m(200) !
476
477 fun_name='male_p';
478 // same implementation, only change is the API
479 c_prog(4:5)=['void '+fun_name+'(void const* const* args, void *const* res) '
480 '{ *((double*)*res)= (double)m((int)((double*)args[0])[0]); }'];
481 make_compiled_function(fun_name, '.c',c_prog);
482 t0=getdate();res=parallel_run(1:200, fun_name);etime(getdate(), t0)// execution time is shorter on multicore]]></programlisting>
483                 <title>Plurality of arguments and returned values</title>
484                 It is of course possible to have more than one argument, and more than one returned value :
485                 <programlisting role="example"><![CDATA[N=10;
486 A=rand(1:N);B=rand(1:N);C=rand(1:N);
487
488
489 f1=['#include <algorithm>',
490 'extern ""C""{',
491 '  void min_med_max_cxx(void const*const* args, void*const* res){',
492 '    double a(((double const*const*)args)[0][0])',
493 '    ,b(((double const*const*)args)[1][0]),c(((double const*const*)args)[2][0]);',
494 '    if(b<a){ std::swap(a,b);}',
495 '    if(c<a){ std::swap(a,c);}',
496 '    if(c<b){ std::swap(b,c);}',
497 '    ((double*const*)res)[0][0]=a; ((double*const*)res)[1][0]=b; ((double*const*)res)[2][0]= c;',
498 '    return;',
499 '  }',
500 '}'];
501
502 mputl(f1,'min_med_max_cxx.cxx');
503 ilib_for_link('min_med_max_cxx','min_med_max_cxx.cxx',[],"c")
504 exec loader.sce
505
506
507 [Min,Med,Max]=parallel_run(A,B,C, "min_med_max_cxx")]]></programlisting>
508                 
509                 
510                 <title>Vector arguments and results</title>
511                 <programlisting role="example"><![CDATA[f1=['#include<algorithm>',
512 'extern ""C""{',
513 ' void quantiles_cxx(void const*const* args, void*const* res){',
514 '   double * data(((double *const*)args)[0]);',
515 '   int data_size(static_cast<int>(((double const*const*)args)[1][0]));',
516 '   double const* ranks(((double *const*)args)[2]);',
517 '   int ranks_size(static_cast<int>(((double *const*)args)[3][0]));',
518 '   std::sort(data,data+data_size);',
519 '   for(int i(0); i != ranks_size; ++i)',
520 '     { ((double*const*)res)[0][i]= data[static_cast<int>(ranks[i])-1]; }',
521 '   return;',
522 ' }',
523 '}'];
524 mputl(f1,'quantiles_cxx.cxx');
525 ilib_for_link('quantiles_cxx','quantiles_cxx.cxx',[],"c")
526 exec loader.sce
527 N=10;
528 data=matrix(rand(1:N*N),N,N);
529 // note the last argument to inform that quantiles returns 3 scalars.
530 R4_6_7=parallel_run(data',N,[4;6;7],3,"quantiles_cxx", 3);]]></programlisting>
531                 
532                 
533             </refsection>
534             
535         </refsection>
536     </refsection>
537     <refsection>
538         <title>Tuning the Parallelization with Configuration Parameters</title>
539         
540         As we have seen in the calling sequence, it is possible to add a configuration parameter as a last argument to parallel_run. This argument is handled by the <literal>params</literal> module and created with<link linkend="init_param">init_param</link>() (further information on how to handle parameters can be found in the help pages of <link linkend="add_param">add_param</link>, <link linkend="set_param">set_param</link> and <link linkend="remove_param">remove_param</link>).
541         
542         <refsection>
543             <title>Number of workers </title>
544             The number of computing resources used in parallel can be set by the parameter <literal>nb_workers</literal>. The default value (0) uses as many workers as there are cores available.
545         </refsection>
546         <refsection>
547             <title>Shared (threads) or separate (process) memory </title>
548             While threads are the most efficient (lightweight) parallelization model, current Scilab implementation does not allow threadsafe code, so threads can only be used for foreign function (which must be threadsafe!) and processes are used for Scilab macros. The behavior is controlled par the <literal>shared_memory</literal> parameter which is a numeric value used as a boolean : threads are used if  <literal>shared_memory</literal> is not 0 and  <literal>f</literal> is a foreign function. This default value (0) implies separate memory (processes).
549         </refsection>
550         <refsection>
551             <title>Dynamic scheduling </title>
552             The number of function calls assigned to each worker can either be static (each worker gets the same number of function calls to do) or dynamic. In the latter case, function calls are assigned to workers when they are available.  The behavior is controlled par the  <literal>dynamic_scheduling</literal> parameter which is a numeric value used as a boolean : dynamic scheduling is used if  <literal>dynamic_scheduling</literal> is not 0. This default value (1) implies dynamic scheduling. Static scheduling is more efficient when there are many calls to the function <literal>f</literal> and they will all take the same amount of time, while dynamic scheduling should be preferred when the amount a time for each call can varies a lot.
553         </refsection>
554         <refsection>
555             <title>Chunk size </title>
556             In order to reduce dynamic scheduling overhead, the n function calls are dispatched in chunks. The chunk size can be set by the parameter <literal>chunk_size</literal>. The default value (0) uses chunks of size 1. For static scheduling, there is virtually no scheduling overhead and the difference between the minimum and maximum number of function calls performed by the workers is at most 1.
557         </refsection>
558         <refsection>
559             <title>Prologue and Epilogue </title>
560             When using separate memory, it is not uncommon to perform process-specific initialization and finalization. For example, when making Monte Carlo simulations, one must insure that each process uses different random number generator initial state, otherwise all the processes would perform the same pseudo-random sequences in parallel ! The  <literal>prologue</literal> (resp. <literal>epilogue</literal>) parameter is a string value either empty ("" default) or containing the name of a macro taking one scalar argument to be run upon starting (resp. ending) a new process (separate memory worker, see above). Each process is given a unique scalar passed as argument to both prologue and epilogue macros.
561             <programlisting role="example"><![CDATA[function init_rand(n)
562   rand('seed',n);
563 endfunction;
564
565 function res= rand_macro(nb)
566   res= rand(1, nb);
567 endfunction
568 nb= 5;
569 // We use a configuration parameter to force nb_workers = 2 even on monocore
570 // without seeding the 2 workers have the same rng state.
571 res= parallel_run([nb,nb],'rand_macro',nb,init_param('nb_workers', 2));
572 // when setting the seed, they should have different random numbers.
573 res= parallel_run([nb,nb],'rand_macro',nb,init_param('nb_workers', 2,'prologue','init_rand')); ]]></programlisting>
574         </refsection>
575         
576         
577     </refsection>
578     <refsection role="see also">
579         <title>See Also</title>
580         <simplelist type="inline">
581             <member>
582                 <link linkend="typeof">typeof</link>
583             </member>
584             <member>
585                 <link linkend="ilib_for_link">ilib_for_link</link>
586             </member>
587             <member>
588                 <link linkend="init_param">init_param</link>
589             </member>
590             <member>
591                 <link linkend="call">call</link>
592             </member>
593         </simplelist>
594     </refsection>
595 </refentry>