Неблокируемая команда `` Isend '' в пакете mpi4py не может перекрывать вычисления и связь - PullRequest
1 голос
/ 30 октября 2019

Уважаемые опытные программисты, пожалуйста, прочитайте мой пост с терпением. Буду искренне признателен за вашу помощь.

Я использую пакет python mpi4py для изучения неблоковой команды связи Isend и Irecv. Я попытался использовать эти две команды для перекрытия связи и последующих вычислений, но следующий код показывает, что при использовании Isend и Irecv не происходит совпадений между вычислениями и связью. Это смутило меня, и я не могу найти значение неблочного общения.

В коде 2 процесса создаются и называются Rank0 и Rank1. Ранг 0 отправляет матрицу Node_r в Ранг 1, и после этого оба ранга выполняют трудоемкий расчет, который не имеет отношения к Node_r. Параметр S контролирует размер Node_r, а S_c контролирует время, затрачиваемое на вычисления.

Если я использую команды Send и Recv. Связь и вычисления не должны перекрываться, и общее время для каждого ранга должно быть суммой времени для каждой части, и результаты моделирования точно подтверждают это.

Если я использую Isend и Irecv, тоЯ ожидаю, что: поскольку эти две команды возвращаются немедленно, процедура вычисления начинается немедленно и в то же время передача данных также началась. Когда каждый ранг завершил вычисление, передача данных должна почти закончиться, и, таким образом, функция Test() или Wait() не должна занимать много времени.

Однако моделирование показывает, что в случае неблокирования, Test() или Wait() занимает то же время, что и Send и Recv. Другими словами, когда два ранга выполняют вычисления, процесс связи не может быть выполнен, и наоборот.

Я пробовал на суперкомпьютере (Iridis 5 в Университете Саутгемптона), а также на моей собственной рабочей станции, нонезависимо от того, я установил большой размер проблемы S = 800, S_c = 1000 (матрица передачи Node_r составляет 8 ГБ) или небольшой случай S = 10, S_c = 3 (Node_r имеет16KB). Использование Isend и Irecv всегда не может ускорить код, то есть на самом деле никакого перекрытия не происходит. Я вставляю код S = 800, S_c = 1000 и результаты:

from mpi4py import MPI
import numpy as np
import time
import random
from sys import getsizeof
from numpy import linalg as LA
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
S =800 # Control the size of matrix Node_r to be sent to Rank1
S_c=1000 #control the time consuming calculation after the data communication 
Maxite =1 # The iteration numbers for task: ``data tranformation-> 
computation'' 
Node_r = np.zeros((4*S,S,S),'float32')
Recv_r = np.zeros((4*S,S,S),'float32')
if rank ==0:
    Node_r = np.zeros((4*S,S,S),'float32')+1.0
    print('Node is of ', getsizeof(Node_r)*1e-3, 'KB')
comm.Barrier()


# Synchronous communication code:
if rank == 0:
    START_rank0 = time.time()
    for i in range(Maxite):
        s_rank0 = time.time()
        req_r = comm.Send( [Node_r, MPI.FLOAT], dest =1, tag =11)
        e1_rank0 = time.time()
        for ii in range(2):
            A = np.random.rand(S_c,S_c)
            tt = LA.eig(A)
        e2_rank0 = time.time()
    END_rank0 = time.time()   
    SendList = [START_rank0,END_rank0,s_rank0,e1_rank0,e2_rank0]     
if rank == 1 :
    START_rank1 = time.time()
    for i in range(Maxite): 
        s_rank1 = time.time()
        recv_r = comm.Irecv([Recv_r, MPI.FLOAT], source=0,tag=11)    
        recv_r.Wait()
        e1_rank1 = time.time()
        for ii in range(2):
            A = np.random.rand(S_c,S_c)
            tt = LA.eig(A)    
        e2_rank1 = time.time()    
    END_rank1 = time.time()   
    RecvList = [START_rank1,END_rank1,s_rank1,e1_rank1,e2_rank1] 
comm.Barrier()

 # Asynchronous communication code:
if rank == 0:
    START2_rank0 = time.time()
    for i in range(Maxite):
        ss_rank0 = time.time()
        req_r = comm.Isend( [Node_r, MPI.FLOAT], dest =1, tag =11)
        req_r.Test()
        ee1_rank0 = time.time()
        for ii in range(2):
            A = np.random.rand(S_c,S_c)
            tt = LA.eig(A)
            req_r.Test()
        ee2_rank0 = time.time()
        if req_r.Test()==False:
            #print('Rank0 Test is False')
            req_r.Wait() 
        ee3_rank0 = time.time() 
    END2_rank0 = time.time()  
    IsendList = [START2_rank0,END2_rank0,ss_rank0,ee1_rank0,ee2_rank0, ee3_rank0]    
if rank == 1 :
    START2_rank1 = time.time()
    for i in range(Maxite): 
        ss_rank1 = time.time()
        recv_r = comm.Irecv([Recv_r, MPI.FLOAT], source=0,tag=11)    
        recv_r.Test()
        ee1_rank1 = time.time()
        for ii in range(2):
            A = np.random.rand(S_c,S_c)
            tt = LA.eig(A)
            recv_r.Test()
        ee2_rank1 = time.time() 
        if recv_r.Test()==False:  
            #print('Rank1 Test is False')
            recv_r.Wait() 
        ee3_rank1 = time.time()
    END2_rank1 = time.time()  
    IrecvList =[START2_rank1,END2_rank1,ss_rank1,ee1_rank1,ee2_rank1,ee3_rank1]
comm.Barrier()
if rank==0:
    print('Send: rank0 Duration',SendList[1]-SendList[0])    
    print('Send: rank0 e1-s Send:',SendList[3]-SendList[2])
    print('Send: rank0 e2-e1 Computation:',SendList[4]-SendList[3]) 
    print('Isend rank0 Duration',IsendList[1]-IsendList[0]) 
    print('Isend rank0 ee1-ss Isend:',IsendList[3]-IsendList[2])
    print('Isend rank0 ee2-ee1 Computation:',IsendList[4]-IsendList[3])
    print('Isend rank0 ee3-ee2 Test/Wait:',IsendList[5]-IsendList[4])    

if rank==1:
    time.sleep(3)
    print('#####################################')
    print('Recv: rank1 Duration',RecvList[1]-RecvList[0])    
    print('Recv: rank1 e1-s Recv:',RecvList[3]-RecvList[2])
    print('Recv: rank1 e2-e1 Computation:',RecvList[4]-RecvList[3])     
    print('Irecv rank1 Duration',IrecvList[1]-IrecvList[0]) 
    print('Irecv rank1 ee1-ss Isend:',IrecvList[3]-IrecvList[2])
    print('Irecv rank1 ee2-ee1 Computation:',IrecvList[4]-IrecvList[3])
    print('Irecv rank1 ee3-ee2 Test/Wait:',IrecvList[5]-IrecvList[4])    

Результаты:

('Node is of ', 8192000.1280000005, 'KB')

('Send: rank0 Duration', 17.594741106033325)
('Send: rank0 e1-s Send:', 5.696147918701172)
('Send: rank0 e2-e1 Computation:', 11.898576974868774)
('Isend rank0 Duration', 17.708693027496338)
('Isend rank0 ee1-ss Isend:', 0.00017595291137695312)
('Isend rank0 ee2-ee1 Computation:', 11.87893009185791)
('Isend rank0 ee3-ee2 Test/Wait:', 5.8295629024505615)

('Recv: rank1 Duration', 17.276994943618774)
('Recv: rank1 e1-s Recv:', 5.695960998535156)
('Recv: rank1 e2-e1 Computation:', 11.581011056900024)
('Irecv rank1 Duration', 17.708734035491943)
('Irecv rank1 ee1-ss Isend:', 3.814697265625e-05)
('Irecv rank1 ee2-ee1 Computation:', 17.708672046661377)
('Irecv rank1 ee3-ee2 Test/Wait:', 1.6927719116210938e-05)

Видно, что неважно Test()или Wait() выполняется, время связи всегда составляет около 5,5 с, а время вычислений составляет около 11,5 с, таким образом, общее время всегда составляет 17 с.

Есть ли способ уменьшить общее время в случае Isend/Irecv, сделав его меньше 17 с?

Я рассмотрел возможную причину, по которой Node_r настолько велик, чтоIsend и Irecv должны работать синхронно, как описано в статье Understanding the Behavior and Performance of Non-blocking Communications in MPI, но я обнаружил, что даже в крошечном случае перекрытие не очевидно.

Я ценю все ваши предложения.

...