Objectif
Compiler et exécuter un fichier de test unitaire avec MPI.
Je suppose que :
- une implémentation de MPI est installée (nous prendrons OpenMPI)
- les bibliothèques Boost MPI et Boost Unit Test Framework sont présentes
- vous connaissez quelques rudiments de CMake
CMake
On utilise le bien connu find_package pour Boost et MPI afin de récupérer tout ce qu'il nous faut pour les headers et les futurs links.
find_package (MPI REQUIRED)
find_package (Boost COMPONENTS mpi REQUIRED)
# Boost dirs for headers and libs.
include_directories (SYSTEM ${Boost_INCLUDE_DIR})
link_directories (${Boost_LIBRARY_DIRS})
Par la suite, on a essentiellement besoin des variables CMake :
- Boost_MPI_LIBRARY pour le link avec Boost::MPI
- MPI_CXX_LIBRARIES pour le link avec la bibliothèque OpenMPI
- MPIEXEC qui nous donne la commande pour lancer un exécutable via MPI
On prend un fichier example_mpi.cpp (des exemples simples sont faciles à trouver sur la Toile). Pour le compiler, on fait :
set(CMAKE_CXX_COMPILER mpicxx)
# MPI example.
add_executable(example_mpi example_mpi.cpp)
target_link_libraries(example_mpi ${MPI_CXX_LIBRARIES})
voire juste
target_link_libraries(example_mpi ${Boost_MPI_LIBRARY})
si on décide d'utiliser la bibliothèque Boost MPI.
Note
mpicxx est une commande qui enrobe le compilateur (g++ par exemple). On dit à CMake d'utiliser mpicxx au lieu du compilo par défaut.
Note
Boost::MPI n'est pas une implémentation de MPI. C'est une bibliothèque plus haut niveau qui s'abstrait de l'implémentation de MPI. Il faut nécessairement en installer une (OpenMPI, LAM/MPI, MPICH2, ...).
L'exemple peut très simplement ressembler à :
#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>
#include <iostream>
namespace mpi = boost::mpi;
int main()
{
mpi::environment env;
mpi::communicator world;
std::cout << "I am process " << world.rank() << " of " << world.size()
<< "." << std::endl;
return 0;
}
Exécuter
Une fois la compilation effectuée, faire simplement :
> mpiexec -np 4 ./example_mpi
pour lancer l'exécutable sur 4 cœurs.
Problème : mais pourquoi tu testes ?
On veut pouvoir faire des exécutables qui soient de vrais tests unitaires et non pas un exemple avec juste une fonction main. De plus, comme j'utilise CMake, je veux pouvoir automatiser le lancement de tous mes exécutables via CTest.
Problème : il faut bien initialiser et bien dire à MPI que j'en ai fini avec toutes mes MPI-series.
En "vrai" MPI, on a :
int main(int argc, char* argv[])
{
MPI_Init(&argc, &argv);
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Code here..
// ... and here
MPI_Finalize();
return 0;
}
Note
C'est ce que fait le constructeur/destructeur de boost::mpi::environment.
En d'autres termes, je veux me faire ma propre fonction main pour l'initialisation de tous mes cas tests Boost (ou avec n'importe quel autre framework de test unitaire C/C++).
La documentation de Boost Unit Test, que je trouve parfois très peu claire avec un manque cruel d'exemple, m'a fait galérer quelques heures avant de trouver quelque chose de simple qui fonctionne.
Conseil : aller regarder les exemples des sources en faisant quelques grep est parfois plus efficace que de trouver la bonne info dans la doc en ligne. On peut aussi en lire sur https://github.com/boostorg/test/tree/master/example
Deux solutions :
- la première que j'ai trouvée dans les tests de Boost::MPI lui-même. Ils utilisent le minimal testing facility. Mais seule la macro BOOST_CHECK est utilisable. Et oubliez les BOOST_CHECK_EQUAL ainsi que l'enregistrement automatique de vos tests dans la suite de tests.
- la deuxième redéfinit la fonction main et appelle boost::unit_test::unit_test_main sans définir ni la macro BOOST_TEST_MODULE ni BOOST_TEST_MAIN qui impliquent la génération automatique de la fonction main par le framework de test (que l'on compile en statique ou dynamique). Pour plus de détails, lire http://www.boost.org/doc/libs/release/libs/test/doc/html/utf/user-guide/test-runners.html
J'utiliserai la deuxième solution.
Note
Ne pensez même pas faire une fixture Boost pour y mettre votre boost::mpi::environment puisque cette dernière sera construite/détruite pour chaque cas test (équivalent du setUp/tearDown). Et il est fort probable que vous ayez ce genre d'erreur :
*** The MPI_Errhandler_set() function was called after MPI_FINALIZE was invoked. *** This is disallowed by the MPI standard. *** Your MPI job will now abort. [hostname:11843] Abort after MPI_FINALIZE completed successfully; not able to guarantee that all other processes were killed!
Un exemple qui marche
On souhaite ici tester que le nombre de procs passés en argument de mpiexec est au moins 2.
Le BOOST_TEST_DYN_LINK dit juste que je vais me lier dynamiquement à Boost::Test.
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>
namespace mpi = boost::mpi;
BOOST_AUTO_TEST_CASE(test_check_world_size)
{
mpi::communicator world;
BOOST_CHECK(world.size() > 1);
}
// (empty) Initialization function. Can't use testing tools here.
bool init_function()
{
return true;
}
int main(int argc, char* argv[])
{
mpi::environment env(argc, argv);
return ::boost::unit_test::unit_test_main( &init_function, argc, argv );
}
On lance tout ça avec un joli mpiexec -np 2 ./test_mpi et on est (presque) content.
Et un peu de CTest pour finir
Une dernière chose : on aimerait dire à CMake via CTest de lancer cet exécutable, mais avec mpiexec et les arguments qui vont bien.
Un cmake --help-command add_test nous indique qu'il est possible de lancer n'importe quel exécutable avec un nombre variable d'arguments. On va donc lui passer : /usr/bin/mpiexec -np NB_PROC ./test_mpi.
Un œil au module FindMPI.cmake nous dit aussi qu'on peut utiliser d'autres variables CMake MPI pour ce genre de chose.
Reprenons donc notre fichier CMake et ajoutons :
add_executable(test_mpi test_mpi.cpp)
target_link_libraries(test_mpi ${Boost_MPI_LIBRARY})
# Number of procs for MPI.
set (PROCS 2)
# Command to launch by CTest. Need the MPI executable and the number of procs.
add_test (test_mpi ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${PROCS}
${MPIEXEC_PREFLAGS}
test_mpi
${MPIEXEC_POSTFLAGS})
où mon exécutable et mon test porte le même nom : test_mpi. On lance le tout avec la commande ctest dans notre dossier de build et hop, le tour est joué !