Documentation of MPI examples.

This commit is contained in:
Benjamin Bouvier 2012-07-16 15:07:48 +02:00
commit b92f17fce5
4 changed files with 219 additions and 21 deletions

View file

@ -1,12 +1,35 @@
//-----------------------------------------------------------------------------
// t-eoMpiParallel.cpp
/*
(c) Thales group, 2012
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact: http://eodev.sourceforge.net
Authors:
Benjamin Bouvier <benjamin.bouvier@gmail.com>
*/
/*
* This file shows an example of parallel evaluation of a population, when using an eoEasyEA algorithm.
* Moreover, we add a basic wrapper on the parallel evaluation, so as to show how to retrieve the best solutions.
*/
//-----------------------------------------------------------------------------
#include <eo>
#include <eoPopEvalFunc.h>
#include <es/make_real.h>
// #include <apply.h>
#include "../real_value.h"
#include <mpi/eoMpi.h>
@ -80,6 +103,16 @@ class eoRealSerializable : public eoReal< eoMinimizingFitness >, public eoserial
typedef eoRealSerializable EOT;
/*
* Wrapper for HandleResponse: shows the best answer, as it is found.
*
* Finding the best solution is an associative operation (as it is based on a "min" function, which is associative too)
* and that's why we can perform it here. Indeed, the min element of 5 elements is the min element of the 3 first
* elements and the min element of the 2 last elements:
* min(1, 2, 3, 4, 5) = min( min(1, 2, 3), min(4, 5) )
*
* This is a reduction. See MapReduce example to have another examples of reduction.
*/
struct CatBestAnswers : public eo::mpi::HandleResponseParallelApply<EOT>
{
CatBestAnswers()
@ -87,14 +120,15 @@ struct CatBestAnswers : public eo::mpi::HandleResponseParallelApply<EOT>
best.fitness( 1000000000. );
}
using eo::mpi::HandleResponseParallelApply<EOT>::_wrapped;
using eo::mpi::HandleResponseParallelApply<EOT>::d;
// if EOT were a template, we would have to do: (thank you C++ :)
// using eo::mpi::HandleResponseParallelApply<EOT>::_wrapped;
// using eo::mpi::HandleResponseParallelApply<EOT>::d;
void operator()(int wrkRank)
{
int index = d->assignedTasks[wrkRank].index;
int size = d->assignedTasks[wrkRank].size;
(*_wrapped)( wrkRank );
(*_wrapped)( wrkRank ); // call to the wrapped function HERE
for(int i = index; i < index+size; ++i)
{
if( best.fitness() < d->data()[ i ].fitness() )
@ -134,6 +168,9 @@ int main(int ac, char** av)
eoEvalFuncPtr< EOT, double, const std::vector< double >& > mainEval( real_value );
eoEvalFuncCounter< EOT > eval( mainEval );
// until this point, everything (but eo::mpi::Node::init) is exactly as in an sequential version.
// We then instanciate the parallel algorithm. The store is directly used by the eoParallelPopLoopEval, which
// internally uses parallel apply.
int rank = eo::mpi::Node::comm().rank();
eo::mpi::DynamicAssignmentAlgorithm assign;
if( rank == eo::mpi::DEFAULT_MASTER )
@ -157,7 +194,7 @@ int main(int ac, char** av)
eo::log << eo::quiet << "DONE!" << std::endl;
} else
{
eoPop< EOT > pop( popSize, init );
eoPop< EOT > pop; // the population doesn't have to be initialized, as it is not used by workers.
eoParallelPopLoopEval< EOT > popEval( assign, eo::mpi::DEFAULT_MASTER, eval );
popEval( pop, pop );
}

View file

@ -1,3 +1,42 @@
/*
(c) Thales group, 2012
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact: http://eodev.sourceforge.net
Authors:
Benjamin Bouvier <benjamin.bouvier@gmail.com>
*/
/*
* This file shows an example of how to make a hierarchy between nodes, when using a parallel apply. In this basic
* test, the master delegates the charge of finding workers to 2 "sub" masters, which then send part of the table to
* their workers.
*
* It's convenient to establish a role map, so as to clearly identify every role:
* - The node 0 is the general master, that delegates the job. It sends the table to the 2 submasters, and waits for the
* results.
* - Nodes 1 and 2 are the worker of the first job: the delegates. They receive the elements of the table and
* retransmit them to the subworkers. They play the roles of worker in the delegating job, and master in the plus one
* job.
* - Following nodes (3 to 6) are workers of the plus one job. They do the real job. Nodes 3 and 5 are attached to
* submaster 1, 4 and 6 to submaster 2.
*
* This test requires exactly 7 hosts. If the size is bigger, an exception will be thrown at the beginning.
**/
# include <mpi/eoMpi.h>
# include <mpi/eoParallelApply.h>
# include <mpi/eoTerminateJob.h>
@ -11,11 +50,7 @@ using namespace std;
using namespace eo::mpi;
// Role map
// 0 : general master
// 1, 2 : worker of general job, master of subjob
// 3 to 7 : workers of subjob
// The real job to execute, for the subworkers: add one to each element of a table.
struct SubWork: public eoUF< int&, void >
{
void operator() ( int & x )
@ -25,14 +60,20 @@ struct SubWork: public eoUF< int&, void >
}
};
// Function called by both subworkers and delegates.
// v is the vector to process, rank is the MPI rank of the sub master
void subtask( vector<int>& v, int rank )
{
// Attach workers according to nodes.
// Submaster with rank 1 will have ranks 3 and 5 as subworkers.
// Submaster with rank 2 will have ranks 4 and 6 as subworkers.
vector<int> workers;
workers.push_back( rank + 2 );
workers.push_back( rank + 4 );
DynamicAssignmentAlgorithm algo( workers );
SubWork sw;
// Launch the job!
ParallelApplyStore<int> store( sw, rank );
store.data( v );
ParallelApply<int> job( algo, rank, store );
@ -40,6 +81,10 @@ void subtask( vector<int>& v, int rank )
EmptyJob stop( algo, rank );
}
// Functor applied by submasters. Wait for the subworkers responses and then add some random processing (here, multiply
// each result by two).
// Note that this work receives a vector of integers as an entry, while subworkers task's operator receives a simple
// integer.
struct Work: public eoUF< vector<int>&, void >
{
void operator() ( vector<int>& v )
@ -57,6 +102,10 @@ int main(int argc, char** argv)
{
// eo::log << eo::setlevel( eo::debug );
Node::init( argc, argv );
if( Node::comm().size() != 7 ) {
throw std::runtime_error("World size should be 7.");
}
vector<int> v;
v.push_back(1);
@ -65,12 +114,18 @@ int main(int argc, char** argv)
v.push_back(7);
v.push_back(42);
// As submasters' operator receives a vector<int> as an input, and ParallelApply takes a vector of
// operator's input as an input, we have to deal with a vector of vector of integers for the master task.
vector< vector<int> > metaV;
// Here, we send twice the same vector. We could also have splitted the first vector into two vectors, one
// containing the beginning and another one containing the end.
metaV.push_back( v );
metaV.push_back( v );
// Assigning roles is done by comparing MPI ranks.
switch( Node::comm().rank() )
{
// Nodes from 0 to 2 are implicated into the delegating task.
case 0:
case 1:
case 2:
@ -95,6 +150,7 @@ int main(int argc, char** argv)
}
break;
// Other nodes are implicated into the subwork task.
default:
{
// all the other nodes are sub workers

View file

@ -1,3 +1,41 @@
/*
(c) Thales group, 2012
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact: http://eodev.sourceforge.net
Authors:
Benjamin Bouvier <benjamin.bouvier@gmail.com>
*/
/*
* This file shows an example of use of parallel apply, in the following context: each element of a table is
* incremented... in a parallel fashion. While this operation is very easy to perform even on a single host, it's just
* an example for parallel apply use.
*
* Besides, this is also a test for assignment (scheduling) algorithms, in different cases. The test succeeds if and
* only if the program terminates without any segfault ; otherwise, there could be a deadlock which prevents the end or
* a segfault at any time.
*
* One important thing is to instanciate an EmptyJob after having launched a ParallelApplyJob, so as the workers to be
* aware that the job is done (as it's a MultiJob).
*
* This test needs at least 3 processes to be launched. Under this size, it will directly throw an exception, at the
* beginning;
*/
# include <mpi/eoMpi.h>
# include <mpi/eoParallelApply.h>
# include <mpi/eoTerminateJob.h>
@ -9,6 +47,9 @@ using namespace std;
using namespace eo::mpi;
/*
* The function to be called on each element of the table: just increment the value.
*/
struct plusOne : public eoUF< int&, void >
{
void operator() ( int & x )
@ -17,22 +58,26 @@ struct plusOne : public eoUF< int&, void >
}
};
/*
* Internal structure representating a test.
*/
struct Test
{
AssignmentAlgorithm * assign;
string description;
int requiredNodesNumber; // nb : chosen nodes ranks must be sequential
AssignmentAlgorithm * assign; // used assignment algorithm for this test.
string description; // textual description of the test
int requiredNodesNumber; // number of required nodes. NB : chosen nodes ranks must be sequential
};
// These tests require at least 3 processes to be launched.
int main(int argc, char** argv)
{
// eo::log << eo::setlevel( eo::debug );
// eo::log << eo::setlevel( eo::debug ); // if you like tty full of rainbows, decomment this line and comment the following one.
eo::log << eo::setlevel( eo::quiet );
bool launchOnlyOne = false ; // Set this to true if you wanna launch only the first test.
Node::init( argc, argv );
// Initializes a vector with random values.
srand( time(0) );
vector<int> v;
for( int i = 0; i < 1000; ++i )
@ -40,18 +85,27 @@ int main(int argc, char** argv)
v.push_back( rand() );
}
// We need to be sure the values are correctly incremented between each test. So as to check this, we save the
// original vector into a variable originalV, and put an offset variable to 0. After each test, the offset is
// incremented and we can compare the returned value of each element to the value of each element in originalV +
// offset. If the two values are different, there has been a problem.
int offset = 0;
vector<int> originalV = v;
// Instanciates the functor to apply on each element
plusOne plusOneInstance;
vector< Test > tests;
const int ALL = Node::comm().size();
if( ALL < 3 ) {
throw std::runtime_error("Needs at least 3 processes to be launched!");
}
// Tests are auto described thanks to member "description"
Test tIntervalStatic;
tIntervalStatic.assign = new StaticAssignmentAlgorithm( 1, REST_OF_THE_WORLD, v.size() );
tIntervalStatic.description = "Correct static assignment with interval.";
tIntervalStatic.description = "Correct static assignment with interval."; // workers have ranks from 1 to size - 1
tIntervalStatic.requiredNodesNumber = ALL;
tests.push_back( tIntervalStatic );
@ -111,23 +165,32 @@ int main(int argc, char** argv)
for( unsigned int i = 0; i < tests.size(); ++i )
{
// Instanciates a store with the functor, the master rank and size of packet (see ParallelApplyStore doc).
ParallelApplyStore< int > store( plusOneInstance, eo::mpi::DEFAULT_MASTER, 3 );
// Updates the contained data
store.data( v );
// Creates the job with the assignment algorithm, the master rank and the store
ParallelApply< int > job( *(tests[i].assign), eo::mpi::DEFAULT_MASTER, store );
// Only master writes information
if( job.isMaster() )
{
cout << "Test : " << tests[i].description << endl;
}
// Workers whose rank is inferior to required nodes number have to run the test, the other haven't anything to
// do.
if( Node::comm().rank() < tests[i].requiredNodesNumber )
{
job.run();
}
// After the job run, the master checks the result with offset and originalV
if( job.isMaster() )
{
EmptyJob stop( *(tests[i].assign), eo::mpi::DEFAULT_MASTER );
// This job has to be instanciated, not launched, so as to tell the workers they're done with the parallel
// job.
EmptyJob stop( *(tests[i].assign), eo::mpi::DEFAULT_MASTER );
++offset;
for(int i = 0; i < v.size(); ++i)
{
@ -141,6 +204,7 @@ int main(int argc, char** argv)
cout << endl;
}
// MPI synchronization (all the processes wait to be here).
Node::comm().barrier();
delete tests[i].assign;

View file

@ -1,3 +1,34 @@
/*
(c) Thales group, 2012
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact: http://eodev.sourceforge.net
Authors:
Benjamin Bouvier <benjamin.bouvier@gmail.com>
*/
/*
* This file shows an example of how to wrap a handler of a job store. Here, the wrapped handler is the "IsFinished"
* one. The only function that has been added is that the wrapper prints a message on standard output, indicating what
* the wrapped function returns as a result.
*
* This test is performed on a parallel apply job, the same as in parallelApply. The main difference is when
* instanciating the store.
*/
# include <mpi/eoMpi.h>
# include <mpi/eoParallelApply.h>
# include <mpi/eoTerminateJob.h>
@ -9,6 +40,7 @@ using namespace std;
using namespace eo::mpi;
// Job functor.
struct plusOne : public eoUF< int&, void >
{
void operator() ( int & x )
@ -17,6 +49,10 @@ struct plusOne : public eoUF< int&, void >
}
};
/*
* Shows the wrapped result of IsFinished, prints a message and returns the wrapped value.
* times is an integer counting how many time the wrapper (hence the wrapped too) has been called.
*/
template< class EOT >
struct ShowWrappedResult : public IsFinishedParallelApply<EOT>
{
@ -39,7 +75,6 @@ struct ShowWrappedResult : public IsFinishedParallelApply<EOT>
int times;
};
// These tests require at least 3 processes to be launched.
int main(int argc, char** argv)
{
// eo::log << eo::setlevel( eo::debug );
@ -63,7 +98,11 @@ int main(int argc, char** argv)
ParallelApplyStore< int > store( plusOneInstance, eo::mpi::DEFAULT_MASTER, 1 );
store.data( v );
store.wrapIsFinished( new ShowWrappedResult<int> );
// This is the only thing which changes: we wrap the IsFinished function.
// According to RAII, we'll delete the invokated wrapper at the end of the main ; the store won't delete it
// automatically.
IsFinishedParallelApply* wrapper = new ShowWrappedResult<int>;
store.wrapIsFinished( wrapper );
ParallelApply<int> job( assign, eo::mpi::DEFAULT_MASTER, store );
// Equivalent to:
@ -86,6 +125,8 @@ int main(int argc, char** argv)
cout << endl;
}
delete wrapper;
return 0;
}