Le document de specification : ============================== Globalement le document de specification correspond a l'implementation qui a ete faite avec : . Transport-ParaMEDMEM qui a ete enrichi avec la classe MPI_Access . Presentation-ParaMEDMEM qui a ete enrichi avec la classe MPI_AccessDEC La conception correspondant a cette specification est restee la meme : . MPI_Access gere pour un ProcessorGroup (IntraCommunicator) : - Les structures MPI_Request et MPI_Status - La valeur des "tags" MPI - Les requetes d'ecritures et de lectures asynchrones - Les communications en "Point a Point" [I]Send, [I]Recv ainsi que [I]SendRecv. - A la difference de l'API MPI [I]SendRecv ne concerne qu'un seul et meme "target". - Les controles de communications asynchrones Wait, Test, WaitAll, TestAll, [I]Probe, Cancel et CancelAll. - Comme c'etait demande seules les methodes "utiles" ont ete implementees. - Les appels a [I]Send ou a [I]Recv avec des sendbuff/recvbuff de valeur NULL ou avec des sendcount/recvcount de valeur nulle sont ignores. - Les methodes de communications collectives ne sont pas implementees dans MPI_Access. - Les deux methodes "Cancel" concernent soit un IRecv deja soumis soit un message en attente (sans IRecv deja soumis). Elles regroupent les differents appels de l'API MPI necessaires (IProbe, IRecv, Wait, Test_Canceled ...). . MPI_AccessDEC utilise les services de MPI_Access pour un ProcessorGroup (IntraCommunicator) et gere : - Les communications collectives en "Point a Point". (AllToAll[v] synchrone ou asynchrone). - Les temps et l'interpolation - Les [I]Send avec leurs buffers (delete []) - Les [I]Recv - La finalisation des envois et receptions de messages dans le destructeur afin qu'il n'y ait plus de message en attente et afin de liberer les buffers MPI_Access et "tags" (ou "MPITags") : ===================================== . Le constructeur permet optionnellement de fixer une plage de tags a utiliser : [BaseTag , MaxTag]. Par defaut c'est [ 0 , MPI_TAG_UB], MPI_TAG_UB etant la valeur maximum d'une implementation de MPI (valeur minimum 32767 soit 2**15-1). Sur awa avec l'implementation lam MPI_TAG_UB vaut 7353944. La norme MPI specifie que cette valeur doit etre la meme dans les process demarres avec mpirun. Dans le cas de l'usage simultane du meme IntraCommunicator dans un meme process (ou de plusieurs IntraCommunicator d'intersection non nulle) cela peut eviter toute ambiguite et aider au debug. . Dans MPI_Access les tags sont constitues de deux parties (#define ModuloTag 10) : + Le dernier digit decimal correspond au MPI_DataType ( 1 pour les messages "temps", 2 pour MPI_INT et 3 pour MPI_DOUBLE) + La valeur des autres digits correspond a une numerotation circulaire des messages. + Un message "temps" et le message de donnees associe ont le meme numero de message (mais des types et donc des tags differents). . Pour un envoi de message d'un process "source" vers un process "target", on dispose de _SendMPITag[target] dans le process source (il contient le dernier "tag" utilise pour l'envoi de messages vers le process target). Et dans le process "target" qui recoit ce message, on dispose de _RecvMPITag[source] (il contient le dernier "tag" utilise pour la reception de messages du process source). Naturellement d'apres la norme MPI les valeurs de ces tags sont les memes. MPI_Access et "RequestIds" : ============================ . ATTENTION : Dans le document de specification, la distinction n'est pas faite clairement entre les "MPITags" (voir ci-dessus) qui sont un argument des appels a MPI et les "RequestIds" qui ne concernent pas les appels MPI. Ces "RequestIds" figurent en effet sous le nom de tag comme argument d'entree/sortie dans l'API de MPI_Access decrite dans le document de specification. Mais dans l'implementation on a bien le nom RequestId (ou bien RecvRequestId/SendRequestId). . Lors de la soumission d'une requete d'ecriture ou de lecture MPI via MPI_Access, on obtient un identifieur "RequestId". Cet identifieur "RequestId" correspond a une structure RequestStruct de MPI_Access a laquelle on accede avec la map "_MapOfRequestStruct". Cette structure RequestStruct permet de gerer MPI_Request et MPI_Status * de MPI et permet d'obtenir des informations sur la requete : target, send/recv, tag, [a]synchrone, type, outcount. . C'est cet identifieur qui peut etre utilise pour controler une requete asynchrone via MPI_Access : Wait, Test, Probe, etc... . En pratique "RequestId" est simplement un entier de l'intervalle [0 , 2**32-1]. Il y a uniquement un compteur cyclique global aussi bien pour les [I]Send que pour les [I]Recv. . Ces "RequestIds" et leur structures associees facilitent les communications asynchrones. Par exemple on a mpi_access->Wait( int RequestId ) au lieu de MPI_Wait(MPI_Request *request, MPI_Status *status) avec gestion de status. . L'API de MPI_Access peut fournir les "SendRequestIds" d'un "target", les "RecvRequestIds" d'un "source" ou bien les "SendRequestIds" de tous les "targets" ou les "RecvRequestIds" de tous les "sources". Cela permet d'eviter leur gestion au niveau de Presentation-ParaMEDMEM. MPI_AccessDEC : =============== . Comme la classe DEC, il est base sur local_group et distant_group ce qui forme un MPI_union_group et donc un IntraCommunicator. . Il permet de choisir le mode synchrone ou asynchrone (par defaut). Le meme programme peut fonctionner en synchrone ou en asynchrone sans devoir etre modifie. . Il permet de choisir un mode d'interpolation (actuellement uniquement une interpolation lineaire) ou bien un mode sans interpolation (par defaut). Ceci pour les communications collectives. Avec interpolation les communications collectives transmettent et recoivent un message "temps" en plus des donnees. . Il implemente AllToAll[v] en "Point a Point" avec ou sans interpolation. . Il gere les buffers d'envoi de messages. Il les detruit donc lorsqu'ils sont disponibles. . Il cree et utilise MPI_Access. MPI_AccessDEC et la gestion des SendBuffers : ============================================= . Comme dans les communications collectives on n'envoie que des parties du meme buffer à chaque process "target", il faut s'assurer en asynchrone que toutes ces parties sont disponibles pour pouvoir liberer le buffer. . On suppose que ces buffers ont ete alloues avec un new double[] . La structure SendBuffStruct permet de conserver l'adresse du buffer et de gerer un compteur de references de ce buffer. Elle comporte aussi MPI_Datatype pour pouvoir faire un delete [] (double *) ... lorsque le compteur est null. . La map _MapOfSendBuffers etablit la correspondance entre chaque RequestId obtenu de MPI_Access->ISend(...) et un SendBuffStruct pour chaque "target" d'une partie du buffer. . Tout cela ne concerne que les envois asynchrones. En synchrone, on detruit senbuf juste apres l'avoir transmis. MPI_AccessDEC et la gestion des RecvBuffers : ============================================= S'il n'y a pas d'interpolation, rien de particulier n'est fait. Avec interpolation pour chaque target : --------------------------------------- . On a _TimeMessages[target] qui est un vecteur de TimesMessages. On en a 2 dans notre cas avec une interpolation lineaire qui contiennent le time(t0)/deltatime precedent et le dernier time(t1)/deltatime. . On a _DataMessages[target] qui est un vecteur de DatasMessages On en a 2 dans notre cas avec une interpolation lineaire qui contiennent les donnees obtenues par Recv au time(t0)/deltatime precedent et au dernier time(t1)/deltatime. . Au temps _t(t*) du processus courrant on effectue l'interpolation entre les valeurs des 2 DatasMessages que l'on rend dans la partie de recvbuf correspondant au target pourvu que t0 < t* <= t1. . Par suite de la difference des "deltatimes" entre process, on peut avoir t0 < t1 < t* auquel cas on aura une extrapolation. . Les vecteurs _OutOfTime, _DataMessagesRecvCount et _DataMessagesType contiennent pour chaque target true si t* > dernier t1, recvcount et MPI_Datatype pour finaliser la gestion des messages a la fin. Etapes des communications collectives de MPI_AccessDEC : ======================================================== AllToAll[v] : Les arguments sont les memes que dans MPI sauf MPI_Comm ------------- inutile (deja connu de MPI_AccessDEC et MPI_Access). Si on a un TimeInterpolator, appel de AllToAll[v]Time. Sinon, on appelle CheckSent pour les echanges asynchrones (voir ci-apres) et on appelle SendRecv pour chaque "target". AllToAll[v]Time : ----------------- . CheckSent() : + appelle SendRequestIds de MPI_Access afin d'obtenir tous les RequestIds d'envoi de messages a tous les "targets". + Pour chaque RequestId, appelle Test de MPI_Access pour savoir si le buffer est libre (flag = true). Lorsqu'il s'agit du FinalCheckSent, on appelle Wait au lieu de Test. + Si le buffer est libre, on decremente le compteur de la structure SendBuffStruct obtenue avec _MapOfSendBuffers. (voir MPI_AccessDEC et la gestion des SendBuffers ci-dessus) + Si le compteur est nul on detruit le TimeMessage ou le SendBuffer en fonction du DataType. + Puis on detruit la structure SendBuffStruct avant de supprimer (erase) cet item de _MapOfSendBuffers . DoSend : + On cree un TimeMessage (voir cette structure dans MPI_Access). + Si l'on est en asynchrone on cree deux structures SendBuffStruct aSendTimeStruct et aSendDataStruct que l'on remplit. + On remplit la structure aSendTimeMessage avec time/deltatime du process courant. "deltatime" doit etre nul s'il s'agit du dernier pas de temps. + Puis pour chaque "target", on envoie le TimeMessage et la partie de sendbuf concernee par ce target. + Si l'on est en asynchrone, on incremente le compteur et on ajoute a _MapOfSendBuffers aSendTimeStruct et aSendDataStruct avec les identifieurs SendTimeRequestId et SendDataRequestId recus de MPI_Access->Send(...). + Et enfin si l'on est en synchrone, on detruit les SendMessages. . CheckTime(recvcount , recvtype , target , UntilEnd) + Au depart, on lit le premier "Message-temps" dans &(*_TimeMessages)[target][1] et le premier message de donnees dans le buffer alloue (*_DataMessages)[target][1]. + Par convention deltatime des messages temps est nul si c'est le dernier. + Boucle while : _t(t*) est le temps courant du processus. "tant que _t(t*) est superieur au temps du "target" (*_TimeMessages)[target][1].time et que (*_TimeMessages)[target][1].deltatime n'est pas nul", ainsi en fin de boucle on aura : _t(t*) <= (*_TimeMessages)[target][1].time avec _t(t*) > (*_TimeMessages)[target][0].time ou bien on aura le dernier message temps du "target". + S'il s'agit de la finalisation des receptions des messages temps et donnees (UntilEnd vaut true), on effectue la boucle jusqu'a ce que l'on trouve (*_TimeMessages)[target][1].deltatime nul. + Dans la boucle : On recopie le dernier message temps dans le message temps precedent et on lit le message temps suivant. On detruit le buffer de donnees du temps precedent. On recopie le pointeur du dernier buffer de donnees dans le precedent. On alloue un nouveau dernier buffer de donnees (*_DataMessages)[target][1] et on lit les donnees correspondantes dans ce buffer. + Si le temps courant du process est plus grand que le dernier temps (*_TimeMessages)[target][1].time du target, on donne la valeur true a (*_OutOfTime)[target]. (*_TimeMessages)[target][1].deltatime est alors nul. . CheckTime + DoRecv + DoInterp + Pour chaque target on appelle CheckTime + Si on a un TimeInterpolator et si le message temps du target n'est pas le premier, on appelle l'interpolateur qui stocke ses resultats dans la partie du buffer de reception qui correspond au "target". + Sinon, on recopie les donnees recues pour ce premier pas de temps dans la partie du buffer de reception qui correspond au "target". Presentation-ParaMEDMEM : ========================= . Des modifications mineures ont ete effectuees dans Presentation-ParaMEDMEM afin de pouvoir utiliser ces nouvelles fonctionnalites. Il n'y a surtout pas eu de bouleversement destabilisateur. L'ancien mode de fonctionnement reste naturellement disponible. . Cela repose sur trois nouvelles options creees avec registerOption dans le constructeur de InterpKernelDEC : + Asynchronous : true ou false (par defaut) + TimeInterpolation : WithoutTimeInterp (par defaut) ou LinearTimeInterp typedef enum{WithoutTimeInterp,LinearTimeInterp} TimeInterpolationMethod; dans MPI_AccessDEC.hxx + AllToAllMethod : Native (par defaut) ou PointToPoint typedef enum{Native,PointToPoint} AllToAllMethod; dans MxN_Mapping.hxx . Le choix des options se fait avec le Data Exchange Channel : + MEDCoupling::InterpKernelDEC dec (*source_group,*target_group); + dec.setOption("Asynchronous",true); + dec.setOption("TimeInterpolation",LinearTimeInterp); + dec.setOption("AllToAllMethod",PointToPoint); . Dans dec.synchronize(), + on cree un objet InterpolationMatrix qui lui-meme cree un objet MxN_Mapping qui lui-meme cree maintenant un objet MPI_AccessDEC + on transmet a MxN_Mapping via l'InterpolationMatrix l'option choisie de AllToAllMethod + on transmet a MPI_AccessDEC les valeurs des options Asynchronous et TimeInterpolation : methodes Asynchronous et SetTimeInterpolator de MPI_AccessDEC. . MEDCoupling::InterpKernelDEC comporte maintenant une surcharge des methodes recvData() et sendData() : + void InterpKernelDEC::recvData( double time ) qui appelle SetTime(time) de MPI_AccessDEC et recvData() + void InterpKernelDEC::sendData( double time , double deltatime ) qui appelle SetTime(time,deltatime) de MPI_AccessDEC et sendData() . recvData() et sendData() de MEDCoupling::InterpKernelDEC appellent multiply et transposeMultiply de l'InterpolationMatrix qui appellent sendRecv et reverseSendRecv de MxN_Mapping qui appellent comm_interface.allToAllV en mode "Native" ou bien MPI_AccessDEC::AllToAllv en mode "PointToPoint"