6

因为到目前为止我没有找到我的问题的答案,并且正处于为这个问题发疯的边缘,所以我只是问了一个折磨我的问题;-)

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

在我的并行程序中,我区分了主进程(在我的情况下为 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

它工作得很好,尤其是当主进程与锁结束的屏障同步时,因为那时主进程的输出是在 put 操作之后进行的。

作为下一步,我尝试让 master 定期检查是否有可用的 slave。因此我创建了一个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);
}

然后这个概念爆炸了。在反转过程并通过主设备从从设备获取所需信息而不是将其从从设备放置到主设备后,我发现我的主要问题是获取锁:解锁命令不成功,因为在 put 的情况下,根本不授予锁,而在 get 的情况下,仅当从属进程完成其工作并在屏障中等待时才授予锁。在我看来,我的想法一定有一个严重的错误。只有当目标进程处于同步整个通信器的屏障中时才能实现锁定,这不可能是被动 RMA 的想法。然后我就可以进行标准的发送/接收操作了。我想要达到的是,该进程 0 一直在委派工作,并且能够通过从属设备的 RMA 确定它可以委派给谁。请有人帮助我并解释我如何在进程 0 上休息以允许其他进程获得锁?

先感谢您!

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

4

2 回答 2

12

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

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

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

出于可移植性和性能原因,应使用以下方式分配窗口的内存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);

第二步:目标内存同步

MPI 标准禁止同时访问窗口中的同一位置(MPI-2.2 规范中的第 11.3 节):

对窗口中的同一内存位置进行并发冲突访问是错误的;如果某个位置由 put 或 accumo 操作更新,则在目标上完成更新操作之前,加载或另一个 RMA 操作无法访问此位置。

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

while (!ready)
{
   MPI_Win_lock(MPI_LOCK_SHARED, 0, 0, win);
   ready = fnz(schedule, oldschedule, size);
   MPI_Win_unlock(0, win);
}

在目标处锁定窗口的另一个原因是为 MPI 库提供条目,从而促进RMA 操作的本地部分的进展。即使在使用不支持 RDMA 的传输(例如 TCP/IP 或共享内存)时, MPI 也提供可移植的 RMA,这需要在目标上完成大量主动工作(称为进程)以支持“被动”RMA。一些库提供了可以在后台进行操作的异步进程线程,例如 Open MPI 配置时--enable-opal-multi-threads(默认禁用),但依赖这种行为会导致不可移植的程序。这就是为什么 MPI 标准允许 put 操作的以下宽松语义(第 11.7 节,第 365 页):

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

如果put 或accumulate 访问与锁同步,那么一旦更新过程执行MPI_WIN_UNLOCK,公共窗口副本的更新就完成了。另一方面,进程内存中私有副本的更新可能会延迟,直到目标进程在该窗口上执行同步调用(6)。因此,对进程内存的更新总是可以延迟到进程执行合适的同步调用。如果使用栅栏或启动后完成等待同步,也可以延迟对公共窗口副本的更新,直到窗口所有者执行同步调用。只有当使用锁同步时,才需要更新公共窗口副本,即使窗口所有者没有执行任何相关的同步调用。

这也在标准的同一部分(第 367 页)的示例 11.12 中进行了说明。事实上,Open MPI 和 Intel MPI都不会更新schedule[]master 代码中的 lock/unlock 调用是否被注释掉的值。MPI 标准进一步建议(§11.7, p. 366):

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

...

lock:如果可能发生冲突,对窗口的更新会受到排他锁的保护。对于本地访问和 RMA 访问,非冲突访问(例如只读访问或累积访问)受共享锁保护。

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

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);

的本地值one因此被转移到rank * sizeof(int)目标窗口开始之后的字节中。如果disp_unit设置为 1,正确的 put 将是:

MPI_Put(&one, 1, MPI_INT, 0, rank * sizeof(int), 1, MPI_INT, win);

第 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 ...

一个完整的 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;
}

具有 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
于 2013-09-12T14:44:59.447 回答
0

如果我使用较新版本的 Open-MPI 库,Hristo Lliev 的答案非常有效。

但是,在我们当前使用的集群上,这是不可能的,并且对于旧版本,最终解锁调用存在死锁行为,如 Hhristo 所述。从某种意义上说,添加选项--mca osc pt2pt确实解决了死锁,但MPI_Win_unlock调用似乎仍然没有完成,直到拥有访问变量的进程自己锁定/解锁窗口。当您的作业完成时间非常不同时,这不是很有用。

因此,从实用的角度来看,虽然严格来说离开了被动 RMA 同步的主题(对此我深表歉意),但我想指出一种解决方法,它为那些坚持使用旧版本的人使用外部文件Open-MPI 库,因此他们不必像我那样浪费太多时间:

您基本上创建了一个外部文件,其中包含有关哪个(从)进程执行哪个作业的信息,而不是内部数组。这样,您甚至不必拥有一个只专门用于记录从属进程的主进程:它也可以执行一项工作。无论如何,每个进程都可以在这个文件中查看接下来要完成的工作,并可能确定一切都已完成。

现在重要的一点是,这个信息文件不会被多个进程同时访问,因为这可能会导致工作重复或更糟。在 MPI 中,窗口的锁定和解锁等价物在这里通过使用锁定文件最容易模仿:该文件由当前访问信息文件的进程创建。其他进程必须通过稍微延迟检查锁定文件是否仍然存在来等待当前进程完成。

完整的信息可以在这里找到。

于 2015-10-05T09:03:37.040 回答