Author: bugman Date: Thu Mar 8 11:26:19 2012 New Revision: 15460 URL: http://svn.gna.org/viewcvs/relax?rev=15460&view=rev Log: Documentation improvements to the multi-processor test implementation. This should make it very easy to see what is happening at all stages. Modified: 1.3/multi/test_implementation.py Modified: 1.3/multi/test_implementation.py URL: http://svn.gna.org/viewcvs/relax/1.3/multi/test_implementation.py?rev=15460&r1=15459&r2=15460&view=diff ============================================================================== --- 1.3/multi/test_implementation.py (original) +++ 1.3/multi/test_implementation.py Thu Mar 8 11:26:19 2012 @@ -1,17 +1,27 @@ -""" -To run in uni-processor mode on a dual core system, change the MULTI variable to False and type: - -$ python parallel_test.py - - -To run in mpi4py multi-processor mode with one master and two slave processors on minimally a dual core system, change the MULTI variable to True and type: - -$ mpiexec -n 3 python parallel_test.py +"""A reference implementation of the multi-processor package. + +Description +=========== + +This is a basic but full implementation of the multi-processor package to demonstrate how it is used. + + +Testing +======= + +To run in uni-processor mode on a dual core system, change the MULTI variable to False and type:: + +$ python test_implementation.py + + +To run in mpi4py multi-processor mode with one master and two slave processors on minimally a dual core system, change the MULTI variable to True and type:: + +$ mpiexec -n 3 python test_implementation.py For a single dual core CPU (Intel Core 2 Duo E8400 at 3.00GHz), the total times averaged over 5 runs are: - - Uni-processor: 31.293 seconds (30.724+30.413+31.197+31.866+32.266) - - Mpi4py-processor: 23.772 seconds (24.854+21.756+22.514+26.899+22.836) - - Scaling efficiency: 1.316 + - Uni-processor: 51.548 seconds (51.054+52.224+51.257+51.112+52.093) + - Mpi4py-processor: 43.185 seconds (43.867+41.478+46.209+39.941+44.429) + - Scaling efficiency: 1.194 """ # Python module imports. @@ -22,15 +32,18 @@ import profile import pstats from random import uniform +import sys + +# Modify the module path. +sys.path.append('..') # relax module imports. from multi import Application_callback, load_multiprocessor, Memo, Processor_box, Result_command, Slave_command -from maths_fns.rotation_matrix import R_random_hypersphere # Module variables. PROFILE = True -MULTI = True +MULTI = False if MULTI: FABRIC = 'mpi4py' PROCESSOR_NUM = 2 @@ -40,56 +53,100 @@ def print_stats(stats, status=0): - """Profiling print out function.""" - + """Profiling print out function, sorting first by total time then by name.""" + + # Sorted print out. pstats.Stats(stats).sort_stats('time', 'name').print_stats() class Main: + """The program.""" + def __init__(self): """Set up some initial variables.""" - self.N = 10000000 + # The total number of calculations to perform by all slave processors. + self.N = 2000000 + + # Variable for counting the completed calculations (to demonstrate slave->master communication). self.num = 0 def run(self): + """This required method executes the entire program.""" + # Initialise the Processor box singleton. processor_box = Processor_box() # Loop over the slaves. num = processor_box.processor.processor_size() for i in range(num): - # Queue the slave command and memo. - processor_box.processor.add_to_queue(Test_slave_command(N=self.N/num), Test_memo(name="Memo_"+repr(i), sum_fn=self.sum_fn)) - - # Execute the calculations. + # Partition out the calculations to one slave. + slave = Test_slave_command(N=self.N/num) + + # Initialise the memo object. + memo = Test_memo(name="Memo_"+repr(i), sum_fn=self.sum_fn) + + # Queue the slave command and its memo. + processor_box.processor.add_to_queue(slave, memo) + + # Execute the calculations, waiting for completion. processor_box.processor.run_queue() - # Print out. + # Final program print out. print("\n\nTotal number of calculations: %s" % self.num) def sum_fn(self, num): + """Method for slave->master communication. + + This is stored in the memo object and used by the result_command on the master (itself invoked by the slave command on the slave processors) to pass the slave data to the master. + + @param num: The number of calculations performed by a given slave processor. + @type num: int + """ + + # Sum the total number of calculations performed on the slaves. self.num += num + class Test_memo(Memo): + """The memo object containing data and functions for the results_command.""" + def __init__(self, name, sum_fn): - """Store some data for the result_command.""" - - # Store the args. + """Store some data for the result command. + + @param name: A name for the memo. + @type name: str + @param sum_fn: A method for summing the number calculations performed by all slaves. + @type sum_fn: method + """ + + # Store the arguments for later use by the result_command. self.name = name self.sum_fn = sum_fn class Test_result_command(Result_command): + """The result command for processing the results from the slaves on the master.""" + def __init__(self, processor, memo_id=None, num=None, completed=True): - """Store all the slave results for processing on the master.""" - - # Execute the base class __init__() method. + """Store all the slave results for processing on the master. + + @param processor: The slave processor object. + @type processor: Processor instance + @keyword memo_id: The ID of the corresponding memo object (currently serves no purpose). + @type memo_id: int + @keyword num: The number of calculations performed by the slave. This is an example of data transfer from the slave to master processor. + @type num: int + @keyword completed: A flag saying if the calculation on the slave processor completed correctly. + @type completed: bool + """ + + # Execute the base class __init__() method (essential). super(Test_result_command, self).__init__(processor=processor, completed=completed) # Store the arguments. @@ -98,25 +155,48 @@ def run(self, processor, memo): - - # Print out. + """Essential method for doing something with the results from the slave processors. + + @param processor: The slave processor object. + @type processor: Processor instance + @param memo: The slave's corresponding memo object. + @type memo: Memo instance + """ + + # Random print out. print("%s, %s calculations completed." % (memo.name, self.num)) - # Calling a function on the master. + # Calling a method on the master. memo.sum_fn(self.num) class Test_slave_command(Slave_command): - def __init__(self, N=None): + """The slave command for use by the slave processor.""" + + def __init__(self, N=0): + """Set up the slave command object for the slave processor. + + @keyword N: The number of calculations for the slave to perform. + @type N: int + """ + + # Store the argument. self.N = N - # Initialise the matrices. + # Initialise some matrices. self.A = zeros((3, 3), float64) self.B = zeros((3, 3), float64) - def run(self, processor, completed): + def run(self, processor, completed=False): + """Essential method for performing calculations on the slave processors. + + @param processor: The slave processor object. + @type processor: Processor instance + @keyword completed: A flag specifying if the slave calculation is completed. This is currently meaningless, but will be passed to this run() method anyway so it needs to be present. + @type completed: bool + """ # Perform some random useless time-consuming stuff. num_calcs = 0 @@ -139,7 +219,6 @@ # Set up the processor. -main = Main() processor = load_multiprocessor(FABRIC, Application_callback(master=Main()), processor_size=PROCESSOR_NUM, verbosity=1) # Run in multi-processor mode. @@ -148,6 +227,8 @@ # Run in multi-processor mode with profiling. else: + # Replace the default profiling print out function. profile.Profile.print_stats = print_stats + + # Execute with profiling. profile.runctx('processor.run()', globals(), locals()) -