/* (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 */ # ifndef __MPI_ASSIGNMENT_ALGORITHM_H__ # define __MPI_ASSIGNMENT_ALGORITHM_H__ # include // std::vector # include "eoMpiNode.h" namespace eo { namespace mpi { /** * @brief Constant indicating to use all the resting available workers, in assignment algorithms constructor * using an interval. */ const int REST_OF_THE_WORLD = -1; /** * @brief Contains informations on the available workers and allows to find assignees for jobs. * * Available workers are workers who aren't processing anything. When they've received an order, workers switch * from the state "available" to the state "busy", and the master has to wait for their response for considering * them available again. * * @ingroup Parallel */ struct AssignmentAlgorithm { /** * @brief Gets the rank of an available worker, so as to send it a task. * * @return The MPI rank of an available worker, or -1 if there is no available worker. */ virtual int get( ) = 0; /** * @brief Gets the number of total available workers. * * Before the first call, it is equal to the total number of present workers, as specified in the * specific assignment algorithm constructor. It allows the Job class to know when all the responses have * been received, by comparing this number to the total number of workers. * * @return Integer indicating how many workers are available. */ virtual int availableWorkers( ) = 0; /** * @brief Reinject the worker of indicated rank in the available state. * * @param wrkRank The MPI rank of the worker who has finished its job. */ virtual void confirm( int wrkRank ) = 0; /** * @brief Indicates who are the workers which do nothing. * * At the end of the algorithm, the master has to warn all the workers that it's done. All the workers mean, * the workers which are currently processing data, and the other ones who could be waiting : the idles. * This function indicates to the master which worker aren't doing anything. * * @return A std::vector containing all the MPI ranks of the idles workers. */ virtual std::vector idles( ) = 0; /** * @brief Reinitializes the assignment algorithm with the right number of runs. * * In fact, this is only useful for static assignment algorithm, which has to be reinitialized every time * it's used, in the case of a Multi Job. It's the user's responsability to call this function. * * @todo Not really clean. Find a better way to do it. */ virtual void reinit( int runs ) = 0; }; /** * @brief Assignment (scheduling) algorithm which handles workers in a queue. * * With this assignment algorithm, workers are put in a queue and may be called an unlimited number of times. * Whenever a worker returns, it is added to the queue, and it becomes available for the next call to get(). * The available workers are all located in the queue at any time, so the number of available workers is * directly equal to the size of the queue. * * This kind of assignment is adapted for tasks whose execution time is stochastic or unknown, but without any * warranty to be faster than other assignments. * * @ingroup Parallel */ struct DynamicAssignmentAlgorithm : public AssignmentAlgorithm { public: /** * @brief Uses all the hosts whose rank is higher to 1, inclusive, as workers. */ DynamicAssignmentAlgorithm( ) { for(int i = 1; i < Node::comm().size(); ++i) { availableWrk.push_back( i ); } } /** * @brief Uses the unique host with given rank as a worker. * * @param unique MPI rank of the unique worker. */ DynamicAssignmentAlgorithm( int unique ) { availableWrk.push_back( unique ); } /** * @brief Uses the workers whose ranks are present in the argument as workers. * * @param workers std::vector containing MPI ranks of workers. */ DynamicAssignmentAlgorithm( const std::vector & workers ) { availableWrk = workers; } /** * @brief Uses a range of ranks as workers. * * @param first The first worker to be included (inclusive) * @param last The last worker to be included (inclusive). If last == eo::mpi::REST_OF_THE_WORLD, all * hosts whose rank is higher than first are taken. */ DynamicAssignmentAlgorithm( int first, int last ) { if( last == REST_OF_THE_WORLD ) { last = Node::comm().size() - 1; } for( int i = first; i <= last; ++i) { availableWrk.push_back( i ); } } virtual int get( ) { int assignee = -1; if (! availableWrk.empty() ) { assignee = availableWrk.back(); availableWrk.pop_back(); } return assignee; } int availableWorkers() { return availableWrk.size(); } void confirm( int rank ) { availableWrk.push_back( rank ); } std::vector idles( ) { return availableWrk; } void reinit( int _ ) { ++_; // nothing to do } protected: std::vector< int > availableWrk; }; /** * @brief Assignment algorithm which gives to each worker a precise number of tasks to do, in a round robin * fashion. * * This scheduling algorithm attributes, at initialization or when calling reinit(), a fixed amount of runs to * distribute to the workers. The amount of runs is then equally distributed between all workers ; if total * number of runs is not a direct multiple of workers number, then remainding unaffected runs are distributed to * workers from the first to the last, in a round-robin fashion. * * This scheduling should be used when the amount of runs can be computed or is fixed and when we guess that the * duration of processing task will be the same for each run. There is no warranty that this algorithm is more * or less efficient that another one. When having a doubt, use DynamicAssignmentAlgorithm. * * @ingroup Parallel */ struct StaticAssignmentAlgorithm : public AssignmentAlgorithm { public: /** * @brief Uses a given precise set of workers. * * @param workers std::vector of MPI ranks of workers which will be used. * @param runs Fixed amount of runs, strictly positive. */ StaticAssignmentAlgorithm( std::vector& workers, int runs ) { init( workers, runs ); } /** * @brief Uses a range of workers. * * @param first The first MPI rank of worker to use * @param last The last MPI rank of worker to use. If it's equal to REST_OF_THE_WORLD, then all the * workers from the first one are taken as workers. * @param runs Fixed amount of runs, strictly positive. */ StaticAssignmentAlgorithm( int first, int last, int runs ) { std::vector workers; if( last == REST_OF_THE_WORLD ) { last = Node::comm().size() - 1; } for(int i = first; i <= last; ++i) { workers.push_back( i ); } init( workers, runs ); } /** * @brief Uses all the hosts whose rank is higher than 1 (inclusive) as workers. * * @param runs Fixed amount of runs, strictly positive. If it's not set, you'll have to call reinit() * later. */ StaticAssignmentAlgorithm( int runs = 0 ) { std::vector workers; for(int i = 1; i < Node::comm().size(); ++i) { workers.push_back( i ); } init( workers, runs ); } /** * @brief Uses an unique host as worker. * * @param unique The MPI rank of the host which will be the worker. * @param runs Fixed amount of runs, strictly positive. */ StaticAssignmentAlgorithm( int unique, int runs ) { std::vector workers; workers.push_back( unique ); init( workers, runs ); } private: /** * @brief Initializes the static scheduling. * * Gives to each worker an equal attribution number, equal to runs / workers.size(), eventually plus one * if number of workers is not a divisor of runs. * * @param workers Vector of hosts' ranks * @param runs Fixed amount of runs, strictly positive. */ void init( std::vector & workers, int runs ) { unsigned int nbWorkers = workers.size(); freeWorkers = nbWorkers; busy.clear(); busy.resize( nbWorkers, false ); realRank = workers; if( runs <= 0 ) { return; } attributions.clear(); attributions.reserve( nbWorkers ); // Let be the euclidean division of runs by nbWorkers : // runs == q * nbWorkers + r, 0 <= r < nbWorkers // This one liner affects q requests to each worker for (unsigned int i = 0; i < nbWorkers; attributions[i++] = runs / nbWorkers) ; // The first line computes r and the one liner affects the remaining // r requests to workers, in ascending order unsigned int diff = runs - (runs / nbWorkers) * nbWorkers; for (unsigned int i = 0; i < diff; ++attributions[i++]); } public: int get( ) { int assignee = -1; for( unsigned i = 0; i < busy.size(); ++i ) { if( !busy[i] && attributions[i] > 0 ) { busy[i] = true; --freeWorkers; assignee = realRank[ i ]; break; } } return assignee; } int availableWorkers( ) { return freeWorkers; } std::vector idles() { std::vector ret; for(unsigned int i = 0; i < busy.size(); ++i) { if( !busy[i] ) { ret.push_back( realRank[i] ); } } return ret; } void confirm( int rank ) { int i = -1; // i is the real index in table for( unsigned int j = 0; j < realRank.size(); ++j ) { if( realRank[j] == rank ) { i = j; break; } } --attributions[ i ]; busy[ i ] = false; ++freeWorkers; } void reinit( int runs ) { init( realRank, runs ); } private: std::vector attributions; std::vector realRank; std::vector busy; unsigned int freeWorkers; }; } } # endif // __MPI_ASSIGNMENT_ALGORITHM_H__