COMUNICAZIONI BIDIREZIONALI

Quando due processi devono scambiarsi dati reciprocamente, si deve fare attenzione alla possibilità di generare punti morti all'interno del codice. Quando infatti si genera un punto morto all'interno del codice, i processi coinvolti con esso non potranno più proseguire. I punti morti si possono generare sia per un errato ordine di chiamata di MPI_SEND o MPI_RECEIVE nelle comunicazioni bidirezionali, sia per una assegnazione insufficiente di memoria di sistema ad un messaggio, sia per la chiamata di una comunicazione collettiva (che ricordo essere bloccante) in una zona del programma esclusa ad uno dei processi appartenenti al comunicatore della funzione chiamata.
In questo contesto parliamo solo dei punti morti generati con le comunicazioni bidirezionali, essendo gli altri semplicemente generati da palesi errori di programmazione.
Ci sono in pratica solo tre casi che possono capitare nella scrittura di una comunicazione bidirezionale:
  1. Entrambi i processi chiamano prima la funzione di SEND e poi quella di RECEIVE
  2. Entrambi i processi chiamano prima la funzione di RECEIVE e poi quella di SEND
  3. Un processo chiama prima la funzione di SEND e poi quella di RECEIVE, mentre l'altro processo coinvolto nella comunicazione chiama le due funzioni con l'ordine opposto
Per ogniuno di questi casi ci sono poi ulteriori possibilità dipendentemente dalla scelta di funzioni bloccanti o non bloccanti; nei seguenti esempi si riportano tutti i possibili casi.

CASO 1: prima SEND e poi RECEIVE
Prendiamo in considerazione questa parte di codice:
 

if(mype.eq.0) then
  call MPI_SEND(tizio,...)
  call MPI_RECV(caio,...)
elsif(mype.eq.1) then
  call MPI_SEND(caio,...)
  call MPI_RECV(tizio)
endif

dove, anche se non esplicitamente segnalato con le variabili sorgentee destinazione nelle funzioni MPI, la comunicazione oggetto delle funzioni MPI chiamate coinvolge il processo 0 ed il processo1.
In questo modo si è generato un possibile punto morto poichè il processo 0 come il processo 1 non potranno mai giungere alla chiamata RECEIVE nel caso che le dimensioni del messaggio da spedire superino le dimensioni massime per un buffer di sistema (cio' che avevo chiamato variabile di sistema): la chiamata SEND usata è infatti bloccante e non termina fino a che la copia dalla variabile del processo a quella di sistema  non e' terminata; se il piu' grande buffer di sistema e' piu' piccolo dei dati da spedire
questo buffer deve essere svuotato dalla chiamata RECEIVE prima che possa essere liberato per proseguire con la spedizione della successiva parte del messaggio.
Per sperimentare questo problema e' possibile provare ad eseguire il seguente codice al variare del parametro n.
 
 

      program puntomorto1
      include 'mpif.h'
      parameter (n=100)
      integer nprocs, mype, ierr, itag, itag2, imesg, imesg2
      integer istatus, i
      dimension imesg(n), imesg2(n)
      dimension istatus(MPI_STATUS_SIZE)

      call MPI_INIT(ierr)
      call MPI_COMM_SIZE(MPI_COMM_WORLD, nprocs, ierr)
      call MPI_COMM_RANK(MPI_COMM_WORLD, mype, ierr)

      itag = 1
      itag2= 2
      do i=1,n
      imesg(i) = mype
      imesg2(i)= mype+1
      enddo
      if(mype.eq.1) then
      call MPI_SEND(imesg(1),n,MPI_INTEGER,0,itag,
     +              MPI_COMM_WORLD,ierr)
      call MPI_RECV(imesg2(1),n,MPI_INTEGER,0,itag2,
     +              MPI_COMM_WORLD,istatus,ierr)
      write(6,*) 'messaggio = ', imesg2(1)
      elseif(mype.eq.0) then
      call MPI_SEND(imesg2(1),n,MPI_INTEGER,1,itag2,
     +              MPI_COMM_WORLD,ierr)
      call MPI_RECV(imesg(1),n,MPI_INTEGER,1,itag,
     +              MPI_COMM_WORLD,istatus,ierr)
      write(6,*) 'messaggio = ', imesg(1)
      endif

      call MPI_FINALIZE(ierr)
      end

Il codice torna a funzionare in modo corretto se invece di una chiamata SEND bloccante se ne usa una non bloccante,
ma con la chimata ad MPI_WAIT non immediatamente dopo la chiamata di MPI_ISEND (infatti MPI_ISEND seguito immediatamente da MPI_WAIT è equivalente ad MPI_SEND):
 

if(mype.eq.0) then
  call MPI_ISEND(tizio,...,ireq,...)
  call MPI_RECV(caio,...)
  call MPI_WAIT(ireq,...)
elsif(mype.eq.1) then
  call MPI_ISEND(caio,...,ireq,...)
  call MPI_RECV(tizio)
  call MPI_WAIT(ireq)
endif

Domanda: era sufficiente che una sola delle due chiamate SEND diventasse non-bloccante (con MPI_WAIT dopo la chimata RECEIVE) per far scomparire il punto morto?
Risposta: prova il seguente con il parametro n sufficientemente grande!
 
 

      program puntomorto2
      include 'mpif.h'
      parameter (n=40000)
      integer nprocs, mype, ierr, itag, itag2, imesg, imesg2
      integer istatus, i
      dimension imesg(n), imesg2(n)
      dimension istatus(MPI_STATUS_SIZE)

      call MPI_INIT(ierr)
      call MPI_COMM_SIZE(MPI_COMM_WORLD, nprocs, ierr)
      call MPI_COMM_RANK(MPI_COMM_WORLD, mype, ierr)

      itag = 1
      itag2= 2
      richiesta=1
      do i=1,n
      imesg(i) = mype
      imesg2(i)= mype+1
      enddo
      if(mype.eq.1) then
      call MPI_ISEND(imesg(1),n,MPI_INTEGER,0,itag,
     +               MPI_COMM_WORLD,richiesta,ierr)
      call MPI_RECV(imesg2(1),n,MPI_INTEGER,0,itag2,
     +              MPI_COMM_WORLD,istatus,ierr)
      call MPI_WAIT(richiesta,ierr)
      write(6,*) 'messaggio = ', imesg2(1)
      elseif(mype.eq.0) then
      call MPI_SEND(imesg2(1),n,MPI_INTEGER,1,itag2,
     +              MPI_COMM_WORLD,ierr)
      call MPI_RECV(imesg(1),n,MPI_INTEGER,1,itag,
     +              MPI_COMM_WORLD,istatus,ierr)
      write(6,*) 'messaggio = ', imesg(1)
      endif

      call MPI_FINALIZE(ierr)
      end

CASO 2: prima RECEIVE e poi SEND
Ancora si ottiene un punto morto per i processi coinvolti nella comunicazione bidirezionale se entrambi chiamano un RECEIVE bloccante prima di un SEND:
 

if(mype.eq.0) then
  call MPI_RECV(caio,...)
  call MPI_SEND(tizio,...)
elsif(mype.eq.1) then
  call MPI_RECV(tizio,...)
  call MPI_SEND(caio,...)
endif

mentre la seguente parte di codice viene eseguita in modo corretto senza punti morti:
 

if(mype.eq.0) then
  call MPI_IRECV(caio,...,ireq,...)
  call MPI_SEND(tizio,...)
  call MPI_WAIT(ireq,...)
elsif(mype.eq.1) then
  call MPI_IRECV(tizio,...,ireq,...)
  call MPI_SEND(caio,...)
  call MPI_WAIT(ireq,...)
endif

CASO 3: SEND e RECEIVE in ordine opposto
In questo caso non c'è la possibilità di generare punti morti sia che si usino funzioni di comunicazione punto a punto bloccanti o non bloccanti.

Considerando quindi sia la possibilità di generare punti morti sia considerando discorsi di prestazioni del codice, nelle comunicazioni bidirezionali è consigliabile usare la seguente parte di codice:
 

if(mype.eq.0) then
  call MPI_ISEND(tizio,...,ireq1,...)
  call MPI_IRECV(caio ,...,ireq2,...)
elsif(mype.eq.1) then
  call MPI_ISEND(caio ,...,ireq1,...)
  call MPI_IRECV(tizio,...,ireq2,...)
endif
call MPI_WAIT(ireq1,...)
call MPI_WAIT(ireq2,...)