Commit 155192b0 authored by Martin Khannouz's avatar Martin Khannouz Committed by Berenger Bramas

Add function to compute execution time and a presentation in orgmode.

parent 2f0d1ef4
This diff is collapsed.
#+TITLE: Usage implicit de MPI dans ScalFmm
#+AUTHOR: Martin Khannouz
#+LANGUAGE: fr
#+STARTUP: inlineimages
#+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:nil -:t f:t *:t <:t
#+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:nil pri:nil tags:not-in-toc
#+EXPORT_SELECT_TAGS: export
#+EXPORT_EXCLUDE_TAGS: noexport
#+TAGS: noexport(n)
# #+BEGIN_SRC sh
# export SCALFMM_DIR=/home/mkhannou/scalfmm
# cd $SCALFMM_DIR
# git checkout mpi_implicit
# spack install scalfmm@src+mpi+starpu \^starpu@svn-trunk+mpi+fxt \^openmpi
# #+END_SRC
* Implémentation mpi implicite très naïve
Cette première version avait pour principal but de découvrir et à prendre en main les fonctions de StarPU MPI.
Les premières étant starpu_mpi_init et starpu_mpi_shutdown. Mais rapidement ont suivies les fonctions pour /tagger/ les /handles/ de StarPU et les ajouter à des nœuds MPI.
À cela c'est ajouté la transformation de tous les appels à starpu_insert_task ou starpu_task_submit par starpu_mpi_insert_task.
Par soucis de simplicité chaque nœud MPI possède l'intégralité de l'arbre, même si ce n'est pas une solution viable sur le long terme.
Pour vérifier que tout fonctionnait correctement, je me suis amusé à /mapper/ toutes les données sur un premier nœud MPI et toutes les tâches sur un second.
J'ai ensuite pu valider que l'arbre du premier nœud avait les bons résultats et que le second nœud n'avait que des erreurs.
* Implémentation moins naïve
Dans l'idée de créer une version 0 un brin potable qui puisse faire du calcul avec plus de deux nœuds MPI, j'ai créé une fonction de /mapping/ des données.
Elle consistait à partager chaque niveau entre tous les processus de la manière la plus équitable possible.
#+CAPTION: Division de chaque niveau entre chaque processus. Groupe de l'arbre de taille 4.
[[./naive_split.png]]
* Reproduction du mapping mpi explicite
Pour pouvoir effectuer des comparaisons il était nécessaire de reproduire le même /mapping/ de tâches la version MPI explicite.
Dans le cas de la version implicite telle qu'elle est actuellement implémentée, le /mapping/ des données infère le /mapping/ de tâches.
La façon la plus simple de procéder est de faire en sorte que les particules se retrouvent sur les mêmes nœuds MPI.
** Premier problème des groupes
La disposition des particules sur les nœuds MPI étant décidé par un tri distribué, il était plus judicieux de sauvegarder le /mapping/ des particules dans un fichier puis de le charger (dans la version implicite) et d'utiliser ce /mapping/ pour influer le /mapping/ au niveau de la version implicite.
Le soucis du tri distribué est qu'il essaye d'équilibrer les particules sur les nœuds sans tenir compte des groupes de l'arbre groupé (/group tree/).
#+CAPTION: Problème issuent de la constitution des groupes.
#+NAME: fig:SED-HR4049
[[./group_issue1.png]]
Or le /mapping/ des données est fait avec la granularité des groupes de l'arbre groupé.
Une première solution serait de modifier un peu l'algorithme de l'arbre pour le forcer à faire des groupes un peu plus petit de telle sorte qu'ils correspondent aux groupes de la version MPI explicite.
Soucis, quand il faudra remonter dans l'arbre, que faire des cellules qui sont présentes sur plusieurs nœuds MPI, que faire de la racine ?
** Solution retenue
Plutôt que d'essayer de reproduire un /mapping/ de données identique à celui de la version explicite quel que soit les particules, nous avons choisi de limiter le nombre de cas reproductibles et de ségmenter ce /mapping/ par niveau.
Ainsi avec un arbre parfait où chaque indice de morton possède le même nombre de particules, il est possible de reproduire le même /mapping/ de données sur un certain de nombre de niveaux.
Ce nombre varie en fonction de la taille des groupes de l'arbre groupé.
#+CAPTION: Méthode pour générer une particule à un indice de Morton donné.
#+NAME: fig:SED-HR4049
[[./morton_box_center.png]]
** Validation des résultats
Pour valider ces résultats, j'ai réutilisé le système de nom de tâches fait pour simgrid. Ainsi j'ai pu indiquer, dans un fichier des informations à propos de chaque tâche.
Les indices de Morton ainsi que les nœuds MPI sur lesquels elles s'exécutent.
*** Observation
Si l'on fait exception des niveaux où l'on sait que des erreurs de tâches se trouveront, on a :
- Plus de tâches dans la version explicite car elle a des tâches (P2P, M2L) symetriques.
- Toutes les tâches issuent de l'algorithme implicite se retrouvent dans l'ensemble des tâches explicite.
- Toutes les tâches sont au moins mapper sur le même nœud MPI. Les tâches symetriques étant parfois mappé sur deux nœuds différents.
* Et après ?
- Comparaison des performances
- Répartition des GFlop
- Répartition du temps de calcul
- Mémoire utilisée par nœud
- Étude d'autres /mapping/
- Limiter l'empreinte mémoire
- Ne pas allouer les cellules numériques si ce n'est pas necessaire (/up/ et /down/)
- Ne pas allouer les cellules symboliques si ce n'est pas necessaire
- Distribuer l'arbre
......@@ -192,8 +192,6 @@ public:
taskNames = new FStarPUTaskNameParams(mpi_rank, nproc);
#endif
#endif
cout << mpi_rank << "/" << nproc << endl;
starpu_malloc_set_align(32);
starpu_pthread_mutex_t initMutex;
......
......@@ -8,6 +8,7 @@
#include <deque>
#include <unordered_set>
#include <unordered_map>
#include <omp.h>
using namespace std;
#include "../../Src/Utils/FGlobal.hpp"
......@@ -49,6 +50,7 @@ struct Task
cout << taskNames[type];
for(size_t i = 0; i < id.size(); ++i)
cout << ", " << id[i];
cout << "(mpi " << mpiNode << ")";
cout << endl;
}
};
......@@ -66,6 +68,7 @@ struct DagData
{
unordered_set<Task> allTask;
unordered_map<long long int, double> performence;
int treeHeight;
};
bool parseLine(DagData & dagData, deque<string> & lineElements)
......@@ -84,6 +87,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[2] = stoll(lineElements[11]);
task.id[3] = stoll(lineElements[12]);
task.mpiNode = stoi(lineElements[13]);
task.level = dagData.treeHeight - 1;
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 10 && lineElements[0] == "P2P")
......@@ -99,6 +103,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
if(task.id[0] == 0 && task.id[1] == 0 && task.id[2] == 0 && task.id[3] == 0)
cout << "Suricate" << endl;
task.mpiNode = stoi(lineElements[9]);
task.level = dagData.treeHeight - 1;
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 10 && lineElements[0] == "M2L" )
......@@ -113,6 +118,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[3] = stoll(lineElements[7]);
task.id[4] = stoll(lineElements[8]);
task.mpiNode = stoi(lineElements[9]);
task.level = task.id[0];
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 13 && lineElements[0] == "M2L_out")
......@@ -127,6 +133,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[3] = stoll(lineElements[10]);
task.id[4] = stoll(lineElements[11]);
task.mpiNode = stoi(lineElements[12]);
task.level = task.id[0];
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 13 && lineElements[0] == "M2M")
......@@ -141,6 +148,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[3] = stoll(lineElements[10]);
task.id[4] = stoll(lineElements[11]);
task.mpiNode = stoi(lineElements[12]);
task.level = task.id[0];
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 13 && lineElements[0] == "L2L")
......@@ -155,6 +163,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[3] = stoll(lineElements[10]);
task.id[4] = stoll(lineElements[11]);
task.mpiNode = stoi(lineElements[12]);
task.level = task.id[0];
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 8 && lineElements[0] == "L2P")
......@@ -166,6 +175,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[0] = stoll(lineElements[5]);
task.id[1] = stoll(lineElements[6]);
task.mpiNode = stoi(lineElements[7]);
task.level = dagData.treeHeight - 1;
dagData.allTask.insert(task);
}
else if(lineElements.size() >= 8 && lineElements[0] == "P2M")
......@@ -177,6 +187,7 @@ bool parseLine(DagData & dagData, deque<string> & lineElements)
task.id[0] = stoll(lineElements[5]);
task.id[1] = stoll(lineElements[6]);
task.mpiNode = stoi(lineElements[7]);
task.level = 0;
dagData.allTask.insert(task);
}
else
......@@ -268,37 +279,91 @@ void fillPerformanceData(const char* const filename, DagData & dagData)
}
fichier.close(); // on ferme le fichier
}
void compareDag(DagData& dag1, DagData& dag2, int treeHeight)
void compareDag(DagData const& dag1, DagData const& dag2, int const treeHeight)
{
long long int notFoundCount[treeHeight] = {0};
bool notFound[treeHeight] = {false};
for(Task task : dag1.allTask)
#pragma omp parallel
{
bool found = false;
if(task.type == P2P || task.type == P2P_OUT || task.type == P2M || task.type == L2P)
notFound[treeHeight-1] = true;
else if(task.id[0] < treeHeight)
++notFound[task.id[0]] = true;
for(Task task2 : dag2.allTask)
#pragma omp single nowait
{
if(task == task2)
long long int notFoundCount[omp_get_num_threads()][treeHeight];
long long int differenceMapping[omp_get_num_threads()][treeHeight];
long long int taskCount[omp_get_num_threads()][treeHeight];
for(int i = 0; i < omp_get_num_threads(); ++i)
{
found = true;
break;
for(int j = 0; j < treeHeight; ++j)
{
notFoundCount[i][j] = 0;
taskCount[i][j] = 0;
differenceMapping[i][j] = 0;
}
}
for(Task task : dag1.allTask)
{
#pragma omp task default(shared) firstprivate(task)
{
bool found = false;
Task sameTask[2];
int sameTaskId = 0;
if(task.level < treeHeight)
++taskCount[omp_get_thread_num()][task.level];
for(auto it = dag2.allTask.begin(); it != dag2.allTask.end(); ++it)
{
if(task == *it)
{
sameTask[sameTaskId++] = *it;
found = true;
if(sameTaskId == 2)
break;
}
}
if(found == false)
{
//task.print();
if(task.level < treeHeight)
++notFoundCount[omp_get_thread_num()][task.level];
}
else
{
bool sameNode = false;
for(int i = 0; i < sameTaskId; ++i)
if(sameTask[i].mpiNode == task.mpiNode)
sameNode = true;
if(!sameNode)
{
#pragma omp critical
{
task.print();
sameTask[0].print();//Il y a au moins une tâche identique trouvée
if(sameTaskId == 2)
sameTask[1].print();//Il y a au moins une tâche identique trouvée
cout << sameTaskId << endl;
cout << endl;
}
if(task.level < treeHeight)
++differenceMapping[omp_get_thread_num()][task.level];
}
}
}
}
#pragma omp taskwait
for(int i = 0; i < treeHeight; ++i)
{
long long int sum = 0;
long long int sumDiffMapping = 0;
long long int sumTaskCount = 0;
for(int j = 0; j < omp_get_num_threads(); ++j)
if(taskCount[j][i] > 0)
{
sum += notFoundCount[j][i];
sumDiffMapping += differenceMapping[j][i];
sumTaskCount += taskCount[j][i];
}
if(sum > 0 || sumDiffMapping > 0)
std::cout << "Diff lvl " << i << " -> " << sum << " (Mapping error : " << sumDiffMapping << "/" << sumTaskCount << ")" << std::endl;
}
}
if(found == false)
{
task.print();
if(task.type == P2P || task.type == P2P_OUT || task.type == P2M || task.type == L2P)
++notFoundCount[treeHeight-1];
else
++notFoundCount[task.id[0]];
}
}
for(int i = 0; i < treeHeight; ++i)
if(notFound[i] == true)
cout << "Diff lvl " << i << " -> " << notFoundCount[i] << endl;
}
int main(int argc, char* argv[])
{
......@@ -334,10 +399,12 @@ int main(int argc, char* argv[])
DagData implicitData, explicitData;
bool implicitGood, explicitGood;
std::thread explicitThread([&](){
explicitData.treeHeight = treeHeight;
fillPerformanceData(explicitTraceFilename, explicitData);
explicitGood = fillDagData(explicitFilename, explicitData);
});
std::thread implicitThread([&](){
implicitData.treeHeight = treeHeight;
fillPerformanceData(implicitTraceFilename, implicitData);
implicitGood = fillDagData(implicitFilename, implicitData);
});
......@@ -347,7 +414,7 @@ int main(int argc, char* argv[])
{
cout << explicitData.allTask.size() << " tasks in explicit." << endl;
cout << implicitData.allTask.size() << " tasks in implicit." << endl;
compareDag(explicitData, implicitData, treeHeight);
compareDag(implicitData, explicitData, treeHeight);
}
return 0;
}
......@@ -39,6 +39,7 @@ using namespace std;
#include "../../Src/Core/FFmmAlgorithm.hpp"
std::vector<MortonIndex> getMortonIndex(const char* const mapping_filename);
void timeAverage(int mpi_rank, int nproc, double elapsedTime);
int main(int argc, char* argv[]){
setenv("STARPU_NCPU","1",1);
......@@ -131,18 +132,22 @@ int main(int argc, char* argv[]){
// Run the algorithm
GroupKernelClass groupkernel;
GroupAlgorithm groupalgo(&groupedTree,&groupkernel, distributedMortonIndex);
FTic timerExecute;
groupalgo.execute();
double elapsedTime = timerExecute.tacAndElapsed();
cout << "Executing time (implicit node " << groupalgo.getRank() << ") " << elapsedTime << "s\n";
timeAverage(groupalgo.getRank(), groupalgo.getNProc(), elapsedTime);
// Usual algorithm
KernelClass kernels; // FTestKernels FBasicKernels
FmmClass algo(&tree,&kernels); //FFmmAlgorithm FFmmAlgorithmThread
algo.execute();
int rank = groupalgo.getRank();
for(int i = 0; i < groupedTree.getHeight(); ++i)
for(int i = 2; i < groupedTree.getHeight(); ++i)//No task at level 0 and 1
{
if(groupedTree.getNbCellGroupAtLevel(i) < groupalgo.getNProc() && rank == 0)
std::cout << "Error at level " << i << std::endl;
}
return 0;
// Validate the result
for(int idxLevel = 2 ; idxLevel < groupedTree.getHeight() ; ++idxLevel){
for(int idxGroup = 0 ; idxGroup < groupedTree.getNbCellGroupAtLevel(idxLevel) ; ++idxGroup){
......@@ -217,3 +222,22 @@ std::vector<MortonIndex> getMortonIndex(const char* const mapping_filename)
cerr << "Impossible d'ouvrir le fichier !" << endl;
return ret;
}
void timeAverage(int mpi_rank, int nproc, double elapsedTime)
{
if(mpi_rank == 0)
{
double sumElapsedTime = elapsedTime;
for(int i = 1; i < nproc; ++i)
{
double tmp;
MPI_Recv(&tmp, 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, 0);
sumElapsedTime += tmp;
}
sumElapsedTime = sumElapsedTime / (double)nproc;
std::cout << "Average time per node : " << sumElapsedTime << "s" << std::endl;
}
else
{
MPI_Send(&elapsedTime, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
}
}
......@@ -43,6 +43,7 @@
void timeAverage(int mpi_rank, int nproc, double elapsedTime);
int main(int argc, char* argv[]){
const FParameterNames LocalOptionBlocSize {
......@@ -128,15 +129,13 @@ int main(int argc, char* argv[]){
mpiComm.global().processId()+1, 0,
mpiComm.global().getComm()), __LINE__);
}
FLOG(std::cout << "My last index is " << leftLimite << "\n");
FLOG(std::cout << "My left limite is " << myLeftLimite << "\n");
for(int i = 0; i < mpiComm.global().processCount(); ++i)
{
if(i == mpiComm.global().processId())
{
std::cout << "My last index is (" << mpiComm.global().processId() << ") " << leftLimite << "\n";
std::cout << "My left limite is (" << mpiComm.global().processId() << ") " << myLeftLimite << "\n";
std::cout << "Size (" << mpiComm.global().processId() << ") " << allParticles.getNbParticles() << "\n";
FLOG(std::cout << "My last index is (" << mpiComm.global().processId() << ") " << leftLimite << "\n");
FLOG(std::cout << "My left limite is (" << mpiComm.global().processId() << ") " << myLeftLimite << "\n");
FLOG(std::cout << "Size (" << mpiComm.global().processId() << ") " << allParticles.getNbParticles() << "\n");
}
mpiComm.global().barrier();
}
......@@ -144,15 +143,16 @@ int main(int argc, char* argv[]){
// Put the data into the tree
GroupOctreeClass groupedTree(NbLevels, loader.getBoxWidth(), loader.getCenterOfBox(), groupSize,
&allParticles, true, leftLimite);
//groupedTree.printInfoBlocks();
// Run the algorithm
GroupKernelClass groupkernel;
GroupAlgorithm groupalgo(mpiComm.global(), &groupedTree,&groupkernel);
FTic timerExecute;
groupalgo.execute();
std::cout << "Wait Others... " << std::endl;
double elapsedTime = timerExecute.tacAndElapsed();
std::cout << "Executing time (explicit node " << mpiComm.global().processId() << ") " << elapsedTime << "s\n";
mpiComm.global().barrier();
timeAverage(mpiComm.global().processId(), mpiComm.global().processCount(), elapsedTime);
groupedTree.forEachCellLeaf<GroupContainerClass>([&](GroupCellClass cell, GroupContainerClass* leaf){
const FSize nbPartsInLeaf = leaf->getNbParticles();
......@@ -209,4 +209,22 @@ int main(int argc, char* argv[]){
return 0;
}
void timeAverage(int mpi_rank, int nproc, double elapsedTime)
{
if(mpi_rank == 0)
{
double sumElapsedTime = elapsedTime;
for(int i = 1; i < nproc; ++i)
{
double tmp;
MPI_Recv(&tmp, 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, 0);
sumElapsedTime += tmp;
}
sumElapsedTime = sumElapsedTime / (double)nproc;
std::cout << "Average time per node : " << sumElapsedTime << "s" << std::endl;
}
else
{
MPI_Send(&elapsedTime, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
}
}
#!/bin/sh
export SCALFMM_SIMGRIDOUT='scalfmm.out'
export GROUP_SIZE=8
export STARPU_STATS=1
export GROUP_SIZE=32
export TREE_HEIGHT=6
export NB_NODE=8
export NB_PARTICLE_PER_NODE=$((`awk "BEGIN{print 8 ** ($TREE_HEIGHT-1)}"` / $NB_NODE))
......@@ -9,28 +10,59 @@ echo "TREE_HEIGHT=$TREE_HEIGHT"
echo "NB_NODE=$NB_NODE"
echo "NB_PARTICLE_PER_NODE=$NB_PARTICLE_PER_NODE"
#Compile only what we need
make testBlockedImplicitAlgorithm generateMapping testBlockedMpiAlgorithm compareDAGmapping -j $((`nproc`*2))
if [ $? -ne 0 ]; then
exit
fi
#Execute explicit mpi version
mpiexec -n $NB_NODE ./Tests/Release/testBlockedMpiAlgorithm -nb $NB_PARTICLE_PER_NODE -bs $GROUP_SIZE -h $TREE_HEIGHT
if [ $? -ne 0 ]; then
exit
fi
#Aggregate task information from explicit execution
a=`ls $SCALFMM_SIMGRIDOUT\_*`
rm -f $SCALFMM_SIMGRIDOUT
echo $a
for i in $a; do
echo $i
cat $i >> $SCALFMM_SIMGRIDOUT
done
#Get task information
cp -f $SCALFMM_SIMGRIDOUT scalfmm_explicit.out
mpiexec -n $NB_NODE ./Tests/Release/generateMapping -nb $NB_PARTICLE_PER_NODE -bs $GROUP_SIZE -h $TREE_HEIGHT
#Get task information from fxt
#a=`ls $STARPU_FXT_PREFIX/../installprof*`
#ARG_FXT_TOO=""
#for i in $a; do
#ARG_FXT_TOO="$ARG_FXT_TOO -i $i"
#done
#$STARPU_DIR/bin/starpu_fxt_tool $ARG_FXT_TOO
#cp $STARPU_FXT_PREFIX/../trace.rec explicit.rec
#rm -f $STARPU_FXT_PREFIX/../installprof*
#Generate mapping for implicite version
mpiexec -n $NB_NODE ./Tests/Release/generateMapping -nb $NB_PARTICLE_PER_NODE -bs $GROUP_SIZE -h $TREE_HEIGHT > /dev/null
#Execute implicit version
mpiexec -n $NB_NODE ./Tests/Release/testBlockedImplicitAlgorithm -map mapping -f canard.fma -bs $GROUP_SIZE -h $TREE_HEIGHT
if [ $? -ne 0 ]; then
exit
fi
#Get task information
cp -f scalfmm.out_0 scalfmm_implicit.out
#Get task information from fxt
a=`ls $STARPU_FXT_PREFIX/../installprof*`
#ARG_FXT_TOO=""
#for i in $a; do
#ARG_FXT_TOO="$ARG_FXT_TOO -i $i"
#done
#$STARPU_DIR/bin/starpu_fxt_tool $ARG_FXT_TOO
#cp $STARPU_FXT_PREFIX/../trace.rec implicit.rec
#rm -f $STARPU_FXT_PREFIX/../installprof*
#Compare DAGs
./Tests/Release/compareDAGmapping -e scalfmm_explicit.out -i scalfmm_implicit.out -h $TREE_HEIGHT > output
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment