带有C的MPI:被动RMA同步

Ben*_*ing 5 c locking mpi unlock

因为到目前为止我没有找到我的问题的答案而且我对这个问题感到疯狂,我只是问这个问题折磨我的想法;-)

我正在研究已经编程的节点消除算法的并行化.目标环境是一个集群.

在我的并行程序中,我区分主进程(在我的情况下为0级)和工作从属(除了0之外的每个等级).我的想法是,主人正在跟踪哪些奴隶可用并发送然后工作.因此,出于某些其他原因,我尝试建立基于具有锁定 - 放置 - 解锁序列的被动RMA的工作流程.我使用一个名为schedule的整数数组,其中表示等级的数组中的每个位置对于工作进程为0或对于可用进程为1(因此如果schedule [1] = 1则可用于工作).如果一个进程完成了它的工作,它将主数据放入1,表示它的可用性.我试过的代码如下:

 MPI_Win_lock(MPI_LOCK_EXCLUSIVE,0,0,win); // a exclusive window is locked on process 0
 printf("Process %d:\t exclusive lock on process 0 started\n",myrank);
 MPI_Put(&schedule[myrank],1,MPI_INT,0,0,1,MPI_INT,win); // the line myrank of schedule is put into process 0
 printf("Process %d:\t put operation called\n",myrank);
 MPI_Win_unlock(0,win); // the window is unlocked
Run Code Online (Sandbox Code Playgroud)

它工作得很好,特别是当主进程与锁定结束同步时,因为然后在put操作之后进行了master的输出.

作为下一步,我试图让主人定期检查是否有可用的奴隶.因此,我创建了一个while循环来重复,直到每个进程都表明它的可用性(我重复说它是程序教我的原则,我知道实现仍然没有做我想要的).循环是基本变体,只是打印我的数组计划,然后检查函数fnz是否还有其他工作进程而不是master:

while(j!=1){
printf("Process %d:\t following schedule evaluated:\n",myrank);
for(i=0;i<size;i++)printf("%d\t",schedule[i]);//print the schedule
printf("\n");
j=fnz(schedule);
}
Run Code Online (Sandbox Code Playgroud)

然后这个概念爆炸了.在反转过程并从主服务器获取所需信息而不是将服务器从从服务器转到主服务器之后我发现我的主要问题是获取锁:解锁命令不成功,因为在put的情况下,根本没有授予锁定,并且在获取锁定的情况下,仅当从属进程完成其工作并在屏障中等待时才授予锁定.在我看来,我的想法中必定存在严重错误.被动RMA的概念不能仅在目标进程处于同步整个通信器的障碍时才能实现锁定.然后我可以继续进行标准的Send/Recv操作.我想要实现的是,过程0一直在委托工作,并且能够由奴隶的RMA识别它可以委派给谁.可以请有人帮助我并解释我如何在进程0上休息以允许其他进程获取锁定?

先感谢您!

更新: 我不确定你是否曾经使用过锁,只是想强调我完全能够获得远程内存窗口的更新副本.如果我从奴隶获得可用性,则仅在奴隶在障碍物中等待时才授予锁定.所以我需要工作的是,进程0执行lock-get-unlock,而进程1和2正在模拟工作,使得进程2占用的时间明显长于1.我期望的结果是进程0打印一个时间表(0,1,0),因为进程0一直没有被询问它是否正在工作,进程1是否完成了工作,进程2仍在工作.在下一步中,当进程2准备就绪时,我期望输出(0,1,1),因为从站都准备好进行新的工作.我得到的是奴隶只有当他们在障碍中等待时才给予进程0锁定,因此,我得到的第一个也是唯一一个输出是我期望的最后一个输出,向我显示锁定是为每个单独的进程首先授予的,当它完成它的工作时.所以,如果有人可以告诉我何时可以通过目标流程授予锁定而不是试图混淆我的知识被动 RMA,我将非常感激

Hri*_*iev 9

首先,被动RMA机制并没有以某种方式神奇地进入远程进程的内存,因为没有多少MPI传输具有真正的RDMA功能,甚至那些(例如InfiniBand)需要大量的非被动参与.目标是为了允许被动RMA操作发生.这在MPI标准中进行了解释,但是以通过RMA窗口公开的内存的公共和私有副本的抽象形式进行了解释.

使用MPI-2实现工作和便携式无源RMA涉及几个步骤.

第1步:目标进程中的窗口分配

出于可移植性和性能原因,应使用MPI_ALLOC_MEM以下命令分配窗口的内存:

int size;
MPI_Comm_rank(MPI_COMM_WORLD, &size);

int *schedule;
MPI_Alloc_mem(size * sizeof(int), MPI_INFO_NULL, &schedule);

for (int i = 0; i < size; i++)
{
   schedule[i] = 0;
}

MPI_Win win;
MPI_Win_create(schedule, size * sizeof(int), sizeof(int), MPI_INFO_NULL,
   MPI_COMM_WORLD, &win);

...

MPI_Win_free(win);
MPI_Free_mem(schedule);
Run Code Online (Sandbox Code Playgroud)

第2步:目标上的内存同步

MPI标准禁止并发访问窗口中的相同位置(来自MPI-2.2规范的第11.3节):

在窗口中对同一个内存位置进行并发冲突访问是错误的.如果通过put或accumulate操作更新位置,则在目标完成更新操作之前,加载或其他RMA操作不能访问此位置.

因此schedule[],目标中的每个访问都必须受到锁的保护(共享,因为它只读取内存位置):

while (!ready)
{
   MPI_Win_lock(MPI_LOCK_SHARED, 0, 0, win);
   ready = fnz(schedule, oldschedule, size);
   MPI_Win_unlock(0, win);
}
Run Code Online (Sandbox Code Playgroud)

将窗口锁定在目标处的另一个原因是将条目提供到MPI库中,从而促进RMA操作的本地部分的进展.即使在使用非RDMA能力的传输(例如TCP/IP或共享存储器)时,MPI也提供便携式 RMA,并且需要在目标上完成大量活动工作(称为进程)以支持"被动"RMA.一些库提供异步进程线程,可以在后台进行操作,例如配置时打开MPI --enable-opal-multi-threads(默认情况下禁用),但依赖此类行为会导致非可移植程序.这就是为什么MPI标准允许put操作的以下宽松语义(§11.7,p.365):

6.当窗口所有者在该窗口上执行对MPI_WIN_WAIT,MPI_WIN_FENCE或MPI_WIN_LOCK的后续调用时,最后通过对公共窗口副本的put或accumulate调用的更新在进程内存中的私有副本中变得可见.

如果put或accumulate访问与锁同步,则一旦更新过程执行MPI_WIN_UNLOCK,公共窗口副本的更新就完成了.另一方面,可以延迟进程存储器中的私有拷贝的更新,直到目标进程在该窗口上执行同步调用(6).因此,对进程存储器的更新总是可以延迟,直到进程执行合适的同步调用.如果使用防护或启动后完成等待同步,则对窗口所有者执行同步调用的更新也可以延迟到公共窗口副本.仅当使用锁同步时,即使窗口所有者未执行任何相关的同步调用,也必须更新公共窗口副本.

这也在例11.12中标准的同一部分(第367页)中说明.实际上,如果主机代码中的锁定/解锁调用被注释掉,则Open MPI和Intel MPI 都不会更新schedule[].MPI标准进一步建议(§11.7,第366页):

给用户的建议.用户可以按照以下规则编写正确的程序:

...

lock:如果窗口的更新可能会发生冲突,则会对它们进行更新保护.非冲突访问(例如只读访问或累积访问)受共享锁保护,包括本地访问和RMA访问.

第3步:MPI_PUT在原点提供正确的参数

MPI_Put(&schedule[myrank],1,MPI_INT,0,0,1,MPI_INT,win);将所有内容转移到目标窗口的第一个元素中.在创建目标窗口的情况下正确调用disp_unit == sizeof(int)是:

int one = 1;
MPI_Put(&one, 1, MPI_INT, 0, rank, 1, MPI_INT, win);
Run Code Online (Sandbox Code Playgroud)

因此,本地值在目标窗口开始之后one被转换为rank * sizeof(int)字节.如果disp_unit设置为1,则正确的放置将是:

MPI_Put(&one, 1, MPI_INT, 0, rank * sizeof(int), 1, MPI_INT, win);
Run Code Online (Sandbox Code Playgroud)

第4步:处理实现细节

以上详细程序与英特尔MPI一起开箱即用.使用Open MPI,必须特别小心.该库围绕一组框架和实现模块构建.的osc(单侧通信)框架有两个实施方式- rdmapt2pt.默认情况下(在Open MPI 1.6.x中可能更早)是rdma由于某种原因它在MPI_WIN_(UN)LOCK调用时不会在目标端进行RMA操作,这会导致类似死锁的行为,除非进行另一次通信调用(MPI_BARRIER在您的情况下) ).另一方面,pt2pt模块按预期进行所有操作.因此,对于Open MPI,必须启动程序,如下所示,以便专门选择pt2pt组件:

$ mpiexec --mca osc pt2pt ...
Run Code Online (Sandbox Code Playgroud)

完整工作的C99示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mpi.h>

// Compares schedule and oldschedule and prints schedule if different
// Also displays the time in seconds since the first invocation
int fnz (int *schedule, int *oldschedule, int size)
{
    static double starttime = -1.0;
    int diff = 0;

    for (int i = 0; i < size; i++)
       diff |= (schedule[i] != oldschedule[i]);

    if (diff)
    {
       int res = 0;

       if (starttime < 0.0) starttime = MPI_Wtime();

       printf("[%6.3f] Schedule:", MPI_Wtime() - starttime);
       for (int i = 0; i < size; i++)
       {
          printf("\t%d", schedule[i]);
          res += schedule[i];
          oldschedule[i] = schedule[i];
       }
       printf("\n");

       return(res == size-1);
    }
    return 0;
}

int main (int argc, char **argv)
{
    MPI_Win win;
    int rank, size;

    MPI_Init(&argc, &argv);

    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (rank == 0)
    {
       int *oldschedule = malloc(size * sizeof(int));
       // Use MPI to allocate memory for the target window
       int *schedule;
       MPI_Alloc_mem(size * sizeof(int), MPI_INFO_NULL, &schedule);

       for (int i = 0; i < size; i++)
       {
          schedule[i] = 0;
          oldschedule[i] = -1;
       }

       // Create a window. Set the displacement unit to sizeof(int) to simplify
       // the addressing at the originator processes
       MPI_Win_create(schedule, size * sizeof(int), sizeof(int), MPI_INFO_NULL,
          MPI_COMM_WORLD, &win);

       int ready = 0;
       while (!ready)
       {
          // Without the lock/unlock schedule stays forever filled with 0s
          MPI_Win_lock(MPI_LOCK_SHARED, 0, 0, win);
          ready = fnz(schedule, oldschedule, size);
          MPI_Win_unlock(0, win);
       }
       printf("All workers checked in using RMA\n");

       // Release the window
       MPI_Win_free(&win);
       // Free the allocated memory
       MPI_Free_mem(schedule);
       free(oldschedule);

       printf("Master done\n");
    }
    else
    {
       int one = 1;

       // Worker processes do not expose memory in the window
       MPI_Win_create(NULL, 0, 1, MPI_INFO_NULL, MPI_COMM_WORLD, &win);

       // Simulate some work based on the rank
       sleep(2*rank);

       // Register with the master
       MPI_Win_lock(MPI_LOCK_EXCLUSIVE, 0, 0, win);
       MPI_Put(&one, 1, MPI_INT, 0, rank, 1, MPI_INT, win);
       MPI_Win_unlock(0, win);

       printf("Worker %d finished RMA\n", rank);

       // Release the window
       MPI_Win_free(&win);

       printf("Worker %d done\n", rank);
    }

    MPI_Finalize();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

样本输出有6个进程:

$ mpiexec --mca osc pt2pt -n 6 rma
[ 0.000] Schedule:      0       0       0       0       0       0
[ 1.995] Schedule:      0       1       0       0       0       0
Worker 1 finished RMA
[ 3.989] Schedule:      0       1       1       0       0       0
Worker 2 finished RMA
[ 5.988] Schedule:      0       1       1       1       0       0
Worker 3 finished RMA
[ 7.995] Schedule:      0       1       1       1       1       0
Worker 4 finished RMA
[ 9.988] Schedule:      0       1       1       1       1       1
All workers checked in using RMA
Worker 5 finished RMA
Worker 5 done
Worker 4 done
Worker 2 done
Worker 1 done
Worker 3 done
Master done
Run Code Online (Sandbox Code Playgroud)