在此操作实例中,您将了解如何在本地计算机和 Microsoft Windows HPC Server 2008 群集上设置和启动 MPI 群集调试程序会话。此操作实例包括使用消息传递界面 (MPI) 和打开多进程 (OpenMP) 应用程序编程界面 (API) 创建应用程序所需的步骤和示例代码。
本指南内容:
本部分的示例代码用于一个使用 Monte Carlo 模拟估算 Pi 值的并行应用程序。
示例代码在每个 MPI 进程上运行 50,000,000 次迭代计算。在每次迭代计算中,示例代码都生成间隔为 [0,1] 的随机数字以确定一组 x 和 y 坐标。然后评估坐标组以确定点是否位于行 x2 + y2 = 1 下。如果点位于该行下,则变量 count 将加一。每个 MPI 的值 count 的总和将成为变量 result。将位于行 (result) 下的总点数乘以四,然后再用所乘的结果除以总迭代计算次数以估算 Pi 值。
以下步骤包含 Monte Carlo 模拟的两个实现。
运行 Visual Studio 2010。
创建一个名为 ParallelPI 的新 C++ Win32 控制台应用程序。使用一个无预编译头的项目。
指定此项目的其他属性。
C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;
C:\Program Files\Microsoft HPC Pack 2008 SDK\Lib\i386;
如果您要构建和调试 64 位应用程序:
C:\Program Files\Microsoft HPC Pack 2008 SDK\Lib\amd64;
msmpi.lib;
在主要源文件中,选择全部代码,然后将其删除。
将以下示例代码之一粘贴到空的源文件中。第一个示例使用的是 MPI 和 OpenMP,第二个示例使用的是 MPI 和并行格式库 (PPL)。
以下代码示例使用的是 MPI 和 OpenMP。函数 ThrowDarts
使用 OpenMP 并行 for
循环,以利用多核硬件(如果可用)。
// ParallelPI.cpp : Defines the entry point for the MPI application.
//
#include "mpi.h"
#include "stdio.h"
#include "stdlib.h"
#include "limits.h"
#include "omp.h"
#include <random>
int ThrowDarts(int iterations)
{
std::tr1::uniform_real<double> MyRandom;
std::tr1::minstd_rand0 MyEngine;
double RandMax = MyRandom.max();
int count = 0;
omp_lock_t MyOmpLock;
omp_init_lock(&MyOmpLock);
//Compute approximation of pi on each node
#pragma omp parallel for
for(int i = 0; i < iterations; ++i)
{
double x, y;
x = MyRandom(MyEngine)/RandMax;
y = MyRandom(MyEngine)/RandMax;
if(x*x + y*y < 1.0)
{
omp_set_lock(&MyOmpLock);
count++;
omp_unset_lock(&MyOmpLock);
}
}
omp_destroy_lock(&MyOmpLock);
return count;
}
int main(int argc, char* argv[])
{
int rank;
int size;
int iterations;
int count;
int result;
double time;
MPI_Status s;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&size);
if(rank == 0)
{
//Rank 0 asks the number of iterations from the user.
iterations = 50000000;
if(argc > 1)
{
iterations = atoi(argv[1]);
}
printf("Executing %d iterations.\n", iterations);
fflush(stdout);
}
//Broadcast the number of iterations to execute.
if(rank == 0)
{
for(int i = 1; i < size; ++i)
{
MPI_Ssend(&iterations, 1, MPI_INT, i, 0, MPI_COMM_WORLD);
}
}
else
{
MPI_Recv(&iterations, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &s);
}
//MPI_Bcast(&iterations, 1, MPI_INT, 0, MPI_COMM_WORLD);
count = ThrowDarts(iterations);
//Gather and sum results
if(rank != 0)
{
MPI_Ssend(&count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
}
else
{
for(int i = 1; i < size; ++i)
{
int TempCount = 0;
MPI_Recv(&TempCount, 1, MPI_INT, i, 0, MPI_COMM_WORLD, &s);
count += TempCount;
}
}
result = count;
//MPI_Reduce(&count, &result, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
if(rank == 0)
{
printf("The value of PI is approximated to be: %16f", 4*((float)result/(float)(iterations*size)));
}
MPI_Barrier(MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
以下代码示例使用并行格式库 (PPL) 而非 OpenMP,使用 MPI 集体操作而非点对点操作。
// ParallelPI.cpp : Defines the entry point for the MPI application.
//
#include "mpi.h"
#include "stdio.h"
#include "stdlib.h"
#include "limits.h"
#include <ppl.h>
#include <random>
#include <time.h>
using namespace Concurrency;
int ThrowDarts(int iterations)
{
combinable<int> count;
int result = 0;
parallel_for(0, iterations, [&](int i){
std::tr1::uniform_real<double> MyRandom;
double RandMax = MyRandom.max();
std::tr1::minstd_rand0 MyEngine;
double x, y;
MyEngine.seed((unsigned int)time(NULL));
x = MyRandom(MyEngine)/RandMax;
y = MyRandom(MyEngine)/RandMax;
if(x*x + y*y < 1.0)
{
count.local() += 1;
}
});
result = count.combine([](int left, int right) { return left + right; });
return result;
}
void main(int argc, char* argv[])
{
int rank;
int size;
int iterations;
int count;
int result;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&size);
if(rank == 0)
{
//Rank 0 reads the number of iterations from the command line.
//50M iterations is the default.
iterations = 50000000;
if(argc > 1)
{
iterations = atoi(argv[argc-1]);
}
printf("Executing %d iterations on %d nodes.\n", iterations, size);
fflush(stdout);
}
//Broadcast the number of iterations to execute.
MPI_Bcast(&iterations, 1, MPI_INT, 0, MPI_COMM_WORLD);
count = ThrowDarts(iterations);
//Gather and sum results
MPI_Reduce(&count, &result, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
if(rank == 0)
{
printf("The value of PI is approximated to be: %16f", 4*((double)result/(double)(iterations*size)));
}
MPI_Barrier(MPI_COMM_WORLD);
MPI_Finalize();
}
在“文件”菜单上单击“全部保存”。
在“生成”菜单上单击“重新生成解决方案”。
在构建您的应用程序后,即可配置和启动调试程序。本节介绍了用于调试的三个选项:
注意:
在 MPI 群集调试程序中,不可未经过调试即启动。按 Ctrl+F5(或在“调试”菜单上选择“启动时不要进行调试”)也可以启动调试。
要在本地计算机上仅使用 MPI 进程进行调试,请使用与要调试任何其他应用程序相同的进程。在程序中希望的位置设置一个断点,然后按 F5 启动调试程序。
MPI 程序通过端口上的 IP 进行通讯。首次启动 MPI 程序时,可能会出现一条防火墙安全警告,显示已经打开一个端口。阅读警告消息并确保了解了您对系统所做的更改。您必须解除防火墙锁定以继续在本地计算机上调试。
以下步骤描述了如何为 ParallelPI 启动本地调试会话。
在运行着四个 MPI 进程的本地计算机上启动 MPI 群集调试程序的步骤
在“解决方案资源管理器”中,右键单击 Parallel PI,然后单击“属性”。此操作将打开“属性页”对话框。
展开“配置属性”,然后选择“调试”。
在“要启动的调试程序”中,选择“MPI 群集调试程序”。
在“运行环境”中,从下拉列表选择“编辑 Hpc 节点”。此操作将打开“节点选择器”对话框。
在“头节点”下拉列表中选择“localhost”。
在“进程数”中选择“4”。
单击“确定”保存更改并关闭“节点选择器”对话框。
ParallelPI 将接受一个确定要运行的迭代计算次数的参数。默认次数为 50,000,000。对于本地调试会话,请根据下面的步骤将迭代次数减少为 5,000:
在“应用程序参数”中键入 5000。
单击“确定”保存更改并关闭“属性页”。
在并行 for
循环代码中设置一个断点。
按 F5 启动调试程序。
将显示五个控制台窗口:一个 cmd.exe 窗口和四个 ParallelPI.exe 窗口(每个窗口用于您启动的每个进程)。与排名 0 进程对应的控制台窗口显示了迭代计算数和所计算的 Pi 估值。
在“调试”菜单上单击“窗口”,然后单击“进程”。
通过双击“进程”窗口中的一个进程以设置用于调试的活动进程。
注意:
当调试多个进程时,默认情况下断点将会影响所有正在调试的进程。为避免在不希望的位置中断各个进程,请取消选择“一个进程中断时中断所有进程”选项。(在“工具”菜单上单击“选项”,然后选择调试)。有关如何更改中断行为的详细信息,请参阅如何:中断执行。
在群集上启动 MPI 调试程序时,调试程序会将您的应用程序作为作业提交给群集。与您的项目 (x86 或 x64、调试或发布)匹配的 Visual C 运行时必须位于计算节点的工作目录中。如果计算节点上没有这些正确的运行时,需通过指定“要部署的其他文件”属性将这些运行时纳入调试程序部署中。以下过程包含部署 OpenMP 调试运行时 DLL 的一个步骤。默认情况下,当启动 MPI 群集调试程序时,C 运行时 (CRT) 库将被部署。如果无正确的运行时,则尝试运行应用程序时将会出现并列错误。如果未包含 OpenMP 运行时,将不会调用断点。
在群集上启动 MPI 调试程序
在“解决方案资源管理器”中,右键单击 Parallel PI,然后单击“属性”。此操作将打开“属性页”对话框。
展开“配置属性”,然后选择“调试”。
在“要启动的调试程序”中,选择“MPI 群集调试程序”。
在“运行环境”中,从下拉列表选择“编辑 Hpc 节点”。此操作将打开“节点选择器”对话框。
在“头节点”下拉列表中,为群集选择要使用的头节点名称。
头节点列表已从 Active Directory 域控制器填充。仅域中的群集会显示在列表中。如果看不到您的头节点,请在属性字段中键入头节点名称或 IPv4 地址。
在“进程数”中选择“4”。
在“计划进程”中,选择如何分配您的进程。您可以选择每“核”、“套接字”或“节点”分配一个进程。
单击“确定”保存更改并关闭“节点选择器”对话框。
在“部署目录”中,在头节点上指定一个共享目录。如果部署目录不存在,并且您拥有指定根目录的写入权限,将自动创建部署目录。
当 HPC Pack 2008 包安装到头节点上时,将创建 CcpSpoolDir 共享资源目录。例如,键入以下内容,其中 <myHeadNode> 是您使用的群集名称:
\\<myHeadNode>\CcpSpoolDir\
在“工作目录”中,在每个计算节点上指定一个本地工作目录。例如,键入以下内容,其中 <myUserName> 是您的用户名:
C:\Users\<myUserName>\ParallelPI
如果使用的是含 OpenMP 的示例代码,请添加 OpenMP 调试运行时 DLL 文件 (Microsoft.VC100.DebugOpenMP\vcomp100d.dll):
单击“确定”保存更改并关闭“属性页”。
在并行 for
循环代码中设置一个断点。
按 F5 启动调试程序。
由于您向群集提交了一份作业,因此系统会提示您输入连接到群集的密码。键入密码,然后按 Enter。
启动调试程序后,请检查进程窗口以验证各个进程的位置。对于每个进程,可通过“传输限定符”列查看进程运行的计算节点。