1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20   
 21   
 22   
 23   
 24  """The multi-processor package. 
 25   
 26  1 Introduction 
 27  ============== 
 28   
 29  This package is an abstraction of specific multi-processor implementations or fabrics such as MPI via mpi4py.  It is designed to be extended for use on other fabrics such as grid computing via SSH tunnelling, threading, etc.  It also has a uni-processor mode as the default fabric. 
 30   
 31   
 32  2 API 
 33  ===== 
 34   
 35  The public API is available via the __init__ module.  It consists of a number of functions and classes.  Using this basic interface, code can be parallelised and executed via an MPI implementation, or default back to a single CPU when needed.  The choice of processor fabric is up to the calling program (via multi.load_multiprocessor). 
 36   
 37   
 38  2.1 Program initialisation 
 39  -------------------------- 
 40   
 41  The function multi.load_multiprocessor() is the interface for how a program can load and set up a specific processor fabric.  This function returns the set up processor, which itself provides a run() method which is used to execute your application. 
 42   
 43   
 44  2.2 Access to the processor instance 
 45  ------------------------------------ 
 46   
 47  The multi.Processor_box class is a special singleton object which provides access to the processor object.  This is required for a number of actions: 
 48   
 49      - Queuing of slave commands and memos via Processor_box().processor.add_to_queue(). 
 50      - Returning results (as a Results_command) from the slave processor to the master via Processor_box().processor.return_object(). 
 51      - Determining the number of processes via Processor_box().processor.processor_size(). 
 52      - Waiting for completion of the queued slave processors via Processor_box().processor.run_queue(). 
 53   
 54   
 55  2.3 Slaves 
 56  ---------- 
 57   
 58  Slave processors are created via the multi.Slave_command class.  This is special base class which must be subclassed.  The run() function should be overridden, this provides the code to execute on the slave processors. 
 59   
 60   
 61  2.4 Results handling 
 62  -------------------- 
 63   
 64  The multi.Result_command class is a special base class which must be subclassed.  The run() function should be overridden, this provides the code for the master to process the results from the slaves. 
 65   
 66  In addition, the multi.Memo should also be used.  This is a special base class which must be subclassed.  This is a data store used by the Results_command to help process the results from the slave on the master processor. 
 67   
 68   
 69   
 70  3 Parallelisation 
 71  ================= 
 72   
 73  The following are the steps required to parallelise a calculation via the multi-processor package API.  It is assumed that the multi.load_multiprocessor() function has been set up at the highest level so that the entire program will be executed by the returned processor's run() method. 
 74   
 75   
 76  3.1 Subclassing command and memo objects 
 77  ---------------------------------------- 
 78   
 79  The first step is that the Slave_command, Result_command, and Memo classes need to be subclassed.  The Slave_command.run() method must be provided and is used for running the calculations on the slave processors.  The Result_command is used to unpack the results from the slave.  It is initialised by the Slave_command itself with the results from the calculation as arguments of __init__().  Its run() method processes the results on the master processor.  The Memo object holds data other than the calculation results required by the Result_command.run() method to process the results. 
 80   
 81   
 82  3.2 Initialisation and queuing 
 83  ------------------------------ 
 84   
 85  The second step is to initialise the Slave_command and Memo and add these to the processor queue.  But first access to the processor is required.  The singleton multi.Processor_box should be imported, and the processor accessed with code such as:: 
 86   
 87      # Initialise the Processor box singleton. 
 88      processor_box = Processor_box()  
 89   
 90  The slave command is then initialised and all required data by the slave for the calculation (via its run() method) is stored within the class instance.  The memo is also initialised with its data required for the result command for processing on the master of the results from the slave.  These are then queued on the processor:: 
 91   
 92      # Queue the slave command and memo. 
 93      processor_box.processor.add_to_queue(command, memo) 
 94   
 95   
 96  3.3 Calculation 
 97  --------------- 
 98   
 99  To execute the calculations, the final part of the calculation code on the master must feature a call to:: 
100   
101      processor_box.processor.run_queue(). 
102   
103   
104  4 Example 
105  ========= 
106   
107  See the script 'test_implementation.py' for a basic example of a reference, and full, implementation of the multi-processor package. 
108   
109   
110  5 Issues 
111  ======== 
112   
113  For multi-core systems and Linux 2.6, the following might be required to prevent the master processor from taking 100% of one CPU core while waiting for the slaves: 
114   
115  # echo "1" > /proc/sys/kernel/sched_compat_yield 
116   
117  This appears to be an OpenMPI problem with late 2.6 Linux kernels. 
118  """ 
119   
120   
121  __all__ = ['memo', 
122             'misc', 
123             'mpi4py_processor', 
124             'multi_processor_base', 
125             'processor', 
126             'processor_io', 
127             'result_commands', 
128             'result_queue', 
129             'slave_commands', 
130             'uni_processor'] 
131   
132   
133  import sys as _sys 
134  import traceback as _traceback 
135   
136   
137  from multi.memo import Memo 
138  from multi.misc import import_module as _import_module 
139  from multi.misc import Verbosity as _Verbosity; _verbosity = _Verbosity() 
140  from multi.result_commands import Result_command 
141  from multi.slave_commands import Slave_command 
142   
143   
144   
145   
146   
148      """Load a multi processor given its name. 
149   
150      Dynamically load a multi processor, the current algorithm is to search in module multi for a 
151      module called <processor_name>.<Processor_name> (note capitalisation). 
152   
153   
154      @todo:  This algorithm needs to be improved to allow users to load processors without altering the relax source code. 
155   
156      @todo:  Remove non-essential parameters. 
157   
158      @param processor_name:  Name of the processor module/class to load. 
159      @type processor_name:   str 
160      @keyword verbosity:     The verbosity level at initialisation.  This can be changed during program execution.  A value of 0 suppresses all output.  A value of 1 causes the basic multi-processor information to be printed.  A value of 2 will switch on a number of debugging printouts.  Values greater than 2 currently do nothing, though this might change in the future. 
161      @type verbosity:        int 
162      @return:                A loaded processor object or None to indicate failure. 
163      @rtype:                 multi.processor.Processor instance 
164      """ 
165   
166       
167      if processor_name not in ['uni', 'mpi4py']: 
168          _sys.stderr.write("The processor type '%s' is not supported.\n" % processor_name) 
169          _sys.exit() 
170   
171       
172      _verbosity.set(verbosity) 
173   
174       
175      processor_name = processor_name + '_processor' 
176      class_name = processor_name[0].upper() + processor_name[1:] 
177      module_path = '.'.join(('multi', processor_name)) 
178   
179       
180      modules = _import_module(module_path) 
181   
182       
183      if hasattr(modules[-1], class_name): 
184          clazz = getattr(modules[-1], class_name) 
185      else: 
186          raise Exception("can't load class %s from module %s" % (class_name, module_path)) 
187   
188       
189      object = clazz(callback=callback, processor_size=processor_size) 
190   
191       
192      processor_box = Processor_box() 
193      processor_box.processor = object 
194      processor_box.processor_name = processor_name 
195      processor_box.class_name = class_name 
196   
197       
198      return object 
 199   
200   
202      """API function for obtaining data from the Processor instance's data store. 
203   
204      This is for fetching data from the data store of the Processor instance.  If run on the master, then the master's data store will be accessed.  If run on the slave, then the slave's data store will be accessed. 
205   
206   
207      @attention:     No inter-processor communications are performed. 
208   
209      @keyword name:  The name of the data structure to fetch. 
210      @type name:     str 
211      @return:        The value of the associated data structure. 
212      @rtype:         anything 
213      """ 
214   
215       
216      processor_box = Processor_box() 
217   
218       
219      return processor_box.processor.fetch_data(name=name) 
 220   
221   
223      """API function for obtaining the data store object from the Processor instance. 
224   
225      If run on the master, then the master's data store will be returned.  If run on the slave, then the slave's data store will be returned. 
226   
227   
228      @attention:     No inter-processor communications are performed. 
229   
230      @return:        The data store of the processor (of the same rank as the calling code). 
231      @rtype:         class instance 
232      """ 
233   
234       
235      processor_box = Processor_box() 
236   
237       
238      return processor_box.processor.data_store 
 239   
240   
242      """API function for sending data from the master to all slaves processors. 
243   
244      @attention:     Inter-processor communications are performed. 
245   
246      @keyword name:  The name of the data structure to store. 
247      @type name:     str 
248      @keyword value: The data structure. 
249      @type value:    anything 
250      """ 
251   
252       
253      processor_box = Processor_box() 
254   
255       
256      processor_box.processor.send_data_to_slaves(name=name, value=value) 
 257   
258   
259   
261      """Call backs provided to the host application by the multi processor framework. 
262   
263      This class allows for independence from the host class/application. 
264   
265      @note:  B{The logic behind the design} the callbacks are defined as two attributes 
266              self.init_master and self.handle_exception as handle_exception can be null (which is 
267              used to request the use of the processors default error handling code). Note, however, 
268              that a class with the equivalent methods would also works as python effectively handles 
269              methods as attributes of a class. The signatures for the callback methods are documented 
270              by the default methods default_init_master & default_handle_exception. 
271      """ 
272   
274          """Initialise the callback interface. 
275   
276          @param master:  The data for the host application. In the default implementation this is an 
277                          object we call methods on but it could be anything... 
278          @type master:   object 
279          """ 
280   
281          self.master = master 
282          """The host application.""" 
283   
284          self.init_master = self.default_init_master 
285          self.handle_exception = self.default_handle_exception 
 286   
287   
289          """Handle an exception raised in the processor framework. 
290   
291          The function is responsible for aborting the processor by calling processor.abort() as its 
292          final act. 
293   
294          @param processor:   The processor instance. 
295          @type processor:    multi.processor.Processor instance 
296          @param exception:   The exception raised by the processor or slave processor. In the case of 
297                              a slave processor exception this may well be a wrapped exception of type 
298                              multi.processor.Capturing_exception which was raised at the point the 
299                              exception was received on the master processor but contains an enclosed 
300                              exception from a slave. 
301          @type exception:    Exception instance 
302          """ 
303   
304           
305          _traceback.print_exc(file=_sys.stderr) 
306   
307           
308          processor.abort() 
 309   
310   
312          """Start the main loop of the host application. 
313   
314          @param processor:   The processor instance. 
315          @type processor:    multi.processor.Processor instance 
316          """ 
317   
318          self.master.run() 
  319   
320   
321   
323      """A storage class for the Processor instance and its attributes. 
324   
325      This singleton contains Processor instances and information about these Processors.  Importantly 
326      this container gives the calling code access to the Processor. 
327      """ 
328   
329       
330      instance = None 
331   
332 -    def __new__(self, *args, **kargs):  
 333          """Replacement function for implementing the singleton design pattern.""" 
334   
335           
336          if self.instance is None: 
337              self.instance = object.__new__(self, *args, **kargs) 
338   
339           
340          return self.instance 
 341