From b92f17fce55b7c602b796b7d796f0eccc4494648 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Mon, 16 Jul 2012 15:07:48 +0200 Subject: [PATCH] Documentation of MPI examples. --- eo/test/mpi/t-mpi-eval.cpp | 51 ++++++++++++++++--- eo/test/mpi/t-mpi-multipleRoles.cpp | 66 ++++++++++++++++++++++-- eo/test/mpi/t-mpi-parallelApply.cpp | 78 ++++++++++++++++++++++++++--- eo/test/mpi/t-mpi-wrapper.cpp | 45 ++++++++++++++++- 4 files changed, 219 insertions(+), 21 deletions(-) diff --git a/eo/test/mpi/t-mpi-eval.cpp b/eo/test/mpi/t-mpi-eval.cpp index 72fca755..e5678104 100644 --- a/eo/test/mpi/t-mpi-eval.cpp +++ b/eo/test/mpi/t-mpi-eval.cpp @@ -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 +*/ + +/* + * 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 #include #include -// #include #include "../real_value.h" #include @@ -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 { CatBestAnswers() @@ -87,14 +120,15 @@ struct CatBestAnswers : public eo::mpi::HandleResponseParallelApply best.fitness( 1000000000. ); } - using eo::mpi::HandleResponseParallelApply::_wrapped; - using eo::mpi::HandleResponseParallelApply::d; + // if EOT were a template, we would have to do: (thank you C++ :) + // using eo::mpi::HandleResponseParallelApply::_wrapped; + // using eo::mpi::HandleResponseParallelApply::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 ); } diff --git a/eo/test/mpi/t-mpi-multipleRoles.cpp b/eo/test/mpi/t-mpi-multipleRoles.cpp index 232a6110..36d588b8 100644 --- a/eo/test/mpi/t-mpi-multipleRoles.cpp +++ b/eo/test/mpi/t-mpi-multipleRoles.cpp @@ -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 +*/ + +/* + * 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 # include # include @@ -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& 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 workers; workers.push_back( rank + 2 ); workers.push_back( rank + 4 ); DynamicAssignmentAlgorithm algo( workers ); SubWork sw; + // Launch the job! ParallelApplyStore store( sw, rank ); store.data( v ); ParallelApply job( algo, rank, store ); @@ -40,6 +81,10 @@ void subtask( vector& 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&, void > { void operator() ( vector& 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 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 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 > 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 diff --git a/eo/test/mpi/t-mpi-parallelApply.cpp b/eo/test/mpi/t-mpi-parallelApply.cpp index 26b0b30e..7cefa203 100644 --- a/eo/test/mpi/t-mpi-parallelApply.cpp +++ b/eo/test/mpi/t-mpi-parallelApply.cpp @@ -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 +*/ + +/* + * 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 # include # include @@ -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 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 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; diff --git a/eo/test/mpi/t-mpi-wrapper.cpp b/eo/test/mpi/t-mpi-wrapper.cpp index 527c7cc6..dbe70261 100644 --- a/eo/test/mpi/t-mpi-wrapper.cpp +++ b/eo/test/mpi/t-mpi-wrapper.cpp @@ -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 +*/ + +/* + * 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 # include # include @@ -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 { @@ -39,7 +75,6 @@ struct ShowWrappedResult : public IsFinishedParallelApply 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 ); + // 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; + store.wrapIsFinished( wrapper ); ParallelApply 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; }