View Javadoc
1   //******************************************************************************
2   //
3   // File:    IntegerSchedule.java
4   // Package: edu.rit.pj
5   // Unit:    Class edu.rit.pj.IntegerSchedule
6   //
7   // This Java source file is copyright (C) 2009 by Alan Kaminsky. All rights
8   // reserved. For further information, contact the author, Alan Kaminsky, at
9   // ark@cs.rit.edu.
10  //
11  // This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
12  // software; you can redistribute it and/or modify it under the terms of the GNU
13  // General Public License as published by the Free Software Foundation; either
14  // version 3 of the License, or (at your option) any later version.
15  //
16  // PJ is distributed in the hope that it will be useful, but WITHOUT ANY
17  // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18  // A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19  //
20  // Linking this library statically or dynamically with other modules is making a
21  // combined work based on this library. Thus, the terms and conditions of the GNU
22  // General Public License cover the whole combination.
23  //
24  // As a special exception, the copyright holders of this library give you
25  // permission to link this library with independent modules to produce an
26  // executable, regardless of the license terms of these independent modules, and
27  // to copy and distribute the resulting executable under terms of your choice,
28  // provided that you also meet, for each linked independent module, the terms
29  // and conditions of the license of that module. An independent module is a module
30  // which is not derived from or based on this library. If you modify this library,
31  // you may extend this exception to your version of the library, but you are not
32  // obligated to do so. If you do not wish to do so, delete this exception
33  // statement from your version.
34  //
35  // A copy of the GNU General Public License is provided in the file gpl.txt. You
36  // may also obtain a copy of the GNU General Public License on the World Wide
37  // Web at http://www.gnu.org/licenses/gpl.html.
38  //
39  //******************************************************************************
40  package edu.rit.pj;
41  
42  import java.lang.reflect.Constructor;
43  import java.lang.reflect.InvocationTargetException;
44  
45  import edu.rit.util.Range;
46  
47  /**
48   * Class IntegerSchedule provides an object that determines how to schedule the
49   * iterations of a {@linkplain ParallelForLoop} among the threads in a
50   * {@linkplain ParallelTeam}. The loop index is type <code>int</code>.
51   * <p>
52   * To create a schedule object, call one of the following static methods:
53   * <UL>
54   * <LI><code>IntegerSchedule.fixed()</code>
55   * <LI><code>IntegerSchedule.dynamic()</code>
56   * <LI><code>IntegerSchedule.guided()</code>
57   * <LI><code>IntegerSchedule.runtime()</code>
58   * <LI><code>IntegerSchedule.parse()</code>
59   * </UL>
60   * <p>
61   * The Parallel Java Library includes three built-in schedule implementations:
62   * fixed, dynamic, and guided. You can create instances of these by calling the
63   * <code>fixed()</code>, <code>dynamic()</code>, and <code>guided()</code> methods. You can
64   * also create your own schedule implementation by writing a subclass of class
65   * IntegerSchedule. The subclass must have a no-argument constructor and a
66   * constructor whose argument is an array of Strings; see the <code>parse()</code>
67   * method for further information about how these constructors are used.
68   *
69   * @author Alan Kaminsky
70   * @version 17-Nov-2009
71   */
72  public abstract class IntegerSchedule
73          extends Schedule {
74  
75      // Hidden data members.
76      // Loop index for ordered() construct.
77      volatile int myOrderedIndex;
78  
79  // Hidden constructors.
80  
81      /**
82       * Construct a new schedule object.
83       */
84      protected IntegerSchedule() {
85          super();
86      }
87  
88  // Exported operations.
89  
90      /**
91       * Returns a fixed schedule object. The loop iterations are apportioned
92       * among the parallel team threads once at the beginning of the parallel for
93       * loop, with each thread getting a fixed number of iterations, the same
94       * number of iterations for each thread (plus or minus one).
95       *
96       * @return Fixed schedule object.
97       */
98      public static IntegerSchedule fixed() {
99          return new FixedIntegerSchedule();
100     }
101 
102     /**
103      * Returns a dynamic schedule object with a chunk size of 1. The loop
104      * iterations are apportioned into chunks of size 1 (one iteration per
105      * chunk). Each parallel team thread repeatedly performs the next available
106      * iteration until there are no more iterations.
107      *
108      * @return Dynamic schedule object.
109      */
110     public static IntegerSchedule dynamic() {
111         return new DynamicIntegerSchedule(1);
112     }
113 
114     /**
115      * Returns a dynamic schedule object with the given chunk size. The loop
116      * iterations are apportioned into chunks of size <code>theChunkSize</code>
117      * (<code>theChunkSize</code> iterations per chunk). Each parallel team thread
118      * repeatedly performs the next available chunk of iterations until there
119      * are no more chunks. The final chunk may be smaller than
120      * <code>theChunkSize</code>.
121      *
122      * @param theChunkSize Chunk size, &gt;= 1.
123      * @return Dynamic schedule object.
124      * @throws java.lang.IllegalArgumentException (unchecked exception) Thrown if
125      *                                  <code>theChunkSize</code> &lt; 1.
126      */
127     public static IntegerSchedule dynamic(int theChunkSize) {
128         return new DynamicIntegerSchedule(theChunkSize);
129     }
130 
131     /**
132      * Returns a self-guided schedule object with a minimum chunk size of 1. The
133      * loop iterations are apportioned into chunks of exponentially diminishing
134      * sizes. Each successive chunk's size is half the remaining number of
135      * iterations divided by the number of threads in the parallel team.
136      * However, each chunk's size is at least 1 (a minimum of one iteration per
137      * chunk). Each parallel team thread repeatedly performs the next available
138      * chunk of iterations until there are no more chunks.
139      *
140      * @return Self-guided schedule object.
141      */
142     public static IntegerSchedule guided() {
143         return new GuidedIntegerSchedule(1);
144     }
145 
146     /**
147      * Returns a self-guided schedule object with the given minimum chunk size.
148      * The loop iterations are apportioned into chunks of exponentially
149      * diminishing sizes. Each successive chunk's size is half the remaining
150      * number of iterations divided by the number of threads in the parallel
151      * team. However, each chunk is at least <code>theChunkSize</code> (a minimum of
152      * <code>theChunkSize</code> iterations per chunk). Each parallel team thread
153      * repeatedly performs the next available chunk of iterations until there
154      * are no more chunks. The final chunk may be smaller than
155      * <code>theChunkSize</code>.
156      *
157      * @param theChunkSize Minimum chunk size, &gt;= 1.
158      * @return Self-guided schedule object.
159      * @throws java.lang.IllegalArgumentException (unchecked exception) Thrown if
160      *                                  <code>theChunkSize</code> &lt; 1.
161      */
162     public static IntegerSchedule guided(int theChunkSize) {
163         return new GuidedIntegerSchedule(theChunkSize);
164     }
165 
166     /**
167      * Returns a schedule object of a type determined at run time. If the
168      * <code>"pj.schedule"</code> Java property is specified, the property's value
169      * is parsed by the <code>parse()</code> method, and that gives the type of
170      * schedule. If the <code>"pj.schedule"</code> Java property is not specified,
171      * the default is a fixed schedule. You can specify the schedule on the Java
172      * command line like this (note that quotation marks may be needed):
173      * <PRE>
174      * java -Dpj.schedule="dynamic(5)" . . .
175      * </PRE>
176      *
177      * @return Schedule object.
178      * @throws java.lang.IllegalArgumentException (unchecked exception) Thrown if the
179      *                                  <code>"pj.schedule"</code> property value cannot be parsed.
180      */
181     public static IntegerSchedule runtime() {
182         return runtime(IntegerSchedule.fixed());
183     }
184 
185     /**
186      * Returns a schedule object of a type determined at run time, using the
187      * given default schedule. If the <code>"pj.schedule"</code> Java property is
188      * specified, the property's value is parsed by the <code>parse()</code> method,
189      * and that gives the type of schedule. If the <code>"pj.schedule"</code> Java
190      * property is not specified, the given <code>defaultSchedule</code> is
191      * returned. You can specify the schedule on the Java command line like this
192      * (note that quotation marks may be needed):
193      * <PRE>
194      * java -Dpj.schedule="dynamic(5)" . . .
195      * </PRE>
196      *
197      * @param defaultSchedule Schedule to use if the <code>"pj.schedule"</code>
198      *                        Java property is not specified.
199      * @return Schedule object.
200      * @throws java.lang.IllegalArgumentException (unchecked exception) Thrown if the
201      *                                  <code>"pj.schedule"</code> property value cannot be parsed.
202      */
203     public static IntegerSchedule runtime(IntegerSchedule defaultSchedule) {
204         String s = PJProperties.getPjSchedule();
205         return s == null ? defaultSchedule : parse(s);
206     }
207 
208     /**
209      * Returns a schedule object of a type determined by parsing the given
210      * string. The string must be one of the following:
211      * <UL>
212      * <LI><code>"fixed"</code> -- Fixed schedule.
213      * <LI><code>"dynamic"</code> -- Dynamic schedule with a chunk size of 1.
214      * <LI><code>"dynamic(<I>n</I>)"</code> -- Dynamic schedule with a chunk size of
215      * <code><I>n</I></code>, an integer &gt;= 1.
216      * <LI><code>"guided"</code> -- Self-guided schedule with a minimum chunk size
217      * of 1.
218      * <LI><code>"guided(<I>n</I>)"</code> -- Self-guided schedule with a minimum
219      * chunk size of <code><I>n</I></code>, an integer &gt;= 1.
220      * <LI><code>"<I>classname</I>"</code> -- Schedule that is an instance of the
221      * given class. <I>classname</I> is the fully-qualified class name of the
222      * schedule class, which must be a subclass of class IntegerSchedule. The
223      * instance is constructed using the subclass's no-argument constructor.
224      * <LI><code>"<I>classname</I>(<I>arg</I>,<I>arg</I>,...)"</code> -- Schedule
225      * that is an instance of the given class. <I>classname</I> is the
226      * fully-qualified class name of the schedule class, which must be a
227      * subclass of class IntegerSchedule. The arguments between the parentheses
228      * are split into separate strings separated by commas. There cannot be
229      * parentheses or commas within the arguments themselves. The instance is
230      * constructed using the subclass's constructor whose argument is an array
231      * of Strings, namely the individual arguments between the parentheses.
232      * </UL>
233      *
234      * @param s String to parse.
235      * @return Schedule object.
236      * @throws java.lang.NullPointerException     (unchecked exception) Thrown if
237      *                                  <code>s</code> is null.
238      * @throws java.lang.IllegalArgumentException (unchecked exception) Thrown if
239      *                                  <code>s</code> is not one of the above.
240      */
241     public static IntegerSchedule parse(String s) {
242         try {
243             int p1 = s.indexOf('(');
244 
245             if (p1 == -1) {
246                 // No arguments. s is the subclass name. Get subclass.
247                 Class<?> subclass
248                         = Class.forName(getSubclassName(s),
249                         true,
250                         Thread.currentThread().getContextClassLoader());
251 
252                 // Instantiate subclass using no-argument constructor.
253                 return (IntegerSchedule) subclass.getConstructor().newInstance();
254             } else {
255                 // Arguments. Verify syntax.
256                 int p2 = s.indexOf(')');
257                 if (p2 != s.length() - 1) {
258                     throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s
259                             + "\" illegal");
260                 }
261 
262                 // Split arguments around commas.
263                 String[] args = s.substring(p1 + 1, p2).split(",");
264 
265                 // s up to '(' is the subclass name. Get subclass.
266                 Class<?> subclass
267                         = Class.forName(getSubclassName(s.substring(0, p1)),
268                         true,
269                         Thread.currentThread().getContextClassLoader());
270 
271                 // Get constructor with one String[] argument.
272                 Constructor<?> constructor
273                         = subclass.getConstructor(String[].class);
274 
275                 // Instantiate subclass using String[]-argument constructor.
276                 return (IntegerSchedule) constructor.newInstance((Object) args);
277             }
278         } catch (ClassCastException exc) {
279             throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s + "\" illegal",
280                     exc);
281         } catch (ClassNotFoundException exc) {
282             throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s + "\" illegal",
283                     exc);
284         } catch (IllegalAccessException exc) {
285             throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s + "\" illegal",
286                     exc);
287         } catch (InstantiationException exc) {
288             throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s + "\" illegal",
289                     exc);
290         } catch (InvocationTargetException exc) {
291             throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s + "\" illegal",
292                     exc);
293         } catch (NoSuchMethodException exc) {
294             throw new IllegalArgumentException("IntegerSchedule.parse(): Schedule \"" + s + "\" illegal",
295                     exc);
296         }
297     }
298 
299     /**
300      * Determine if this schedule is a fixed schedule. For a parallel team with
301      * <I>K</I> threads, a fixed schedule partitions the loop index range into
302      * exactly <I>K</I> chunks, one chunk for each thread, each chunk with
303      * predetermined upper and lower bounds.
304      *
305      * @return True if this is a fixed schedule, false otherwise.
306      */
307     public abstract boolean isFixedSchedule();
308 
309 // Hidden operations.
310 
311     /**
312      * Get the name of the subclass to instantiate. The names <code>"fixed"</code>,
313      * <code>"dynamic"</code>, and <code>"guided"</code> are recognized as special
314      * cases.
315      *
316      * @param name Subclass name, or special case string.
317      * @return Subclass name.
318      */
319     private static String getSubclassName(String name) {
320         if (name.equals("fixed")) {
321             return "edu.rit.pj.FixedIntegerSchedule";
322         } else if (name.equals("dynamic")) {
323             return "edu.rit.pj.DynamicIntegerSchedule";
324         } else if (name.equals("guided")) {
325             return "edu.rit.pj.GuidedIntegerSchedule";
326         } else {
327             return name;
328         }
329     }
330 
331     /**
332      * Start a parallel for loop using this schedule. This method performs
333      * common processing, then calls the subclass-specific <code>start()</code>
334      * method.
335      *
336      * @param K            Number of threads in the parallel team.
337      * @param theLoopRange Range of iterations for the entire parallel for loop.
338      *                     The stride may be 1 or greater.
339      */
340     void commonStart(int K,
341                      Range theLoopRange) {
342         myBreak = false;
343         myOrderedIndex = theLoopRange.lb();
344         start(K, theLoopRange);
345     }
346 
347     /**
348      * Start generating chunks of iterations for a parallel for loop using this
349      * schedule.
350      * <p>
351      * The <code>start()</code> method is only called by a single thread in the
352      * Parallel Java middleware.
353      *
354      * @param K            Number of threads in the parallel team.
355      * @param theLoopRange Range of iterations for the entire parallel for loop.
356      *                     The stride may be 1 or greater.
357      */
358     public abstract void start(int K,
359                                Range theLoopRange);
360 
361     /**
362      * Obtain the next chunk of iterations for the given thread index. This
363      * method performs common processing, then calls the subclass-specific
364      * <code>next()</code> method.
365      *
366      * @param theThreadIndex Thread index in the range 0 .. <I>K</I>-1.
367      * @return Chunk of iterations, or null if no more iterations.
368      */
369     Range commonNext(int theThreadIndex) {
370         if (myBreak) {
371             return null;
372         } else {
373             return next(theThreadIndex);
374         }
375     }
376 
377     /**
378      * Obtain the next chunk of iterations for the given thread index. If there
379      * are more iterations, a range object is returned whose lower bound, upper
380      * bound, and stride specify the chunk of iterations to perform. The
381      * returned range object's stride is the same as that given to the
382      * <code>start()</code> method. The returned range object's lower bound and
383      * upper bound are contained within the range given to the <code>start()</code>
384      * method. If there are no more iterations, null is returned.
385      * <p>
386      * The <code>next()</code> method is called by multiple parallel team threads in
387      * the Parallel Java middleware. The <code>next()</code> method must be multiple
388      * thread safe.
389      *
390      * @param theThreadIndex Thread index in the range 0 .. <I>K</I>-1.
391      * @return Chunk of iterations, or null if no more iterations.
392      */
393     public abstract Range next(int theThreadIndex);
394 
395 // Unit test main program.
396 //	/**
397 //	 * Unit test main program.
398 //	 */
399 //	public static void main
400 //		(String[] args)
401 //		{
402 //		if (args.length != 4)
403 //			{
404 //			System.err.println ("Usage: java [-Dpj.schedule=<SCHEDULE>] edu.rit.pj.IntegerSchedule <K> <lb> <ub> <stride>");
405 //			System.exit (1);
406 //			}
407 //		int K = Integer.parseInt (args[0]);
408 //		int lb = Integer.parseInt (args[1]);
409 //		int ub = Integer.parseInt (args[2]);
410 //		int stride = Integer.parseInt (args[3]);
411 //		IntegerSchedule schedule = IntegerSchedule.runtime();
412 //		schedule.start (K, new Range (lb, ub, stride));
413 //		Range chunk;
414 //		for (int k = 0; k < K; ++ k)
415 //			{
416 //			while ((chunk = schedule.next (k)) != null)
417 //				{
418 //				System.out.println ("Thread " + k + " chunk = " + chunk);
419 //				}
420 //			}
421 //		}
422 }