gpt4 book ai didi

c++ - 将 openMP 与 openMPI 混合时的奇怪行为

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:02:03 27 4
gpt4 key购买 nike

我有一些使用 openMP(在 for 循环上)并行化的代码。我现在想多次重复该功能并使用 MPI 提交到机器集群,同时保持节点内的所有内容仍然是 openMP。

当我只使用 openMP 时,我得到了预期的速度(使用两倍的处理器/内核数量,用一半的时间完成)。当我添加 MPI 并仅提交给一个 MPI 进程时,我没有得到这种加速。我创建了一个玩具问题来检查这个并且仍然有同样的问题。这是代码

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include "mpi.h"

#include <omp.h>


int main(int argc, char *argv[]) {
int iam=0, np = 1;
long i;
int numprocs, rank, namelen;
char processor_name[MPI_MAX_PROCESSOR_NAME];

double t1 = MPI_Wtime();
std::cout << "!!!Hello World!!!" << std::endl; // prints !!!Hello World!!!

MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Get_processor_name(processor_name, &namelen);

int nThread = omp_get_num_procs();//omp_get_num_threads here returns 1??
printf("nThread = %d\n", nThread);

int *total = new int[nThread];
for (int j=0;j<nThread;j++) {
total[j]=0;
}
#pragma omp parallel num_threads(nThread) default(shared) private(iam, i)
{
np = omp_get_num_threads();

#pragma omp for schedule(dynamic, 1)
for (i=0; i<10000000; i++) {
iam = omp_get_thread_num();
total[iam]++;
}
printf("Hello from thread %d out of %d from process %d out of %d on %s\n",
iam, np, rank, numprocs,processor_name);
}

int grandTotal=0;
for (int j=0;j<nThread;j++) {
printf("Total=%d\n",total[j]);
grandTotal += total[j];
}
printf("GrandTotal= %d\n", grandTotal);

MPI_Finalize();

double t2 = MPI_Wtime();

printf("time elapsed with MPI clock=%f\n", t2-t1);
return 0;
}

我正在使用 openmpi-1.8/bin/mpic++ 进行编译,使用 -fopenmp 标志。这是我的 PBS 脚本

#PBS -l select=1:ncpus=12

setenv OMP_NUM_THREADS 12

/util/mpi/openmpi-1.8/bin/mpirun -np 1 -hostfile $PBS_NODEFILE --map-by node:pe=$OMP_NUM_THREADS /workspace/HelloWorldMPI/HelloWorldMPI

我也尝试过使用#PBS -l nodes=1:ppn=12,得到相同的结果。

当使用一半的核心时,程序实际上更快(快两倍!)。当我减少内核数量时,我更改了 ncpus 和 OMP_NUM_THREADS。我尝试增加实际工作(添加 10^10 个数字而不是代码中显示的 10^7)。我试过删除 printf 语句,想知道它们是否以某种方式减慢了速度,但仍然有同样的问题。顶部显示我正在使用接近 100% 的所有 CPU(在 ncpus 中设置)。如果我使用 -np=2 提交,它会在两台机器上完美地并行化,所以 MPI 似乎按预期工作,但 openMP 坏了

现在没有想法,我可以尝试任何东西。我做错了什么?

最佳答案

我不想这么说,但有很多错误,你应该熟悉一下自己更多地使用 OpenMP 和 MPI。不过,我会尝试通过你的代码并指出我看到的错误。

double t1 = MPI_Wtime();

开始:在 MPI_Init() 之前调用 MPI_Wtime() 是未定义的。我还要补充一点,如果你想用 MPI 做这种基准测试,一个好主意是在之前放一个 MPI_Barrier()调用 Wtime 以便所有任务同时进入该部分。

//omp_get_num_threads here returns 1??

omp_get_num_threads() 返回 1 的原因是你不在平行区域。

#pragma omp parallel num_threads(nThread)

您将 num_threads 设置为 nThread ,正如 Hristo Iliev 提到的那样,有效忽略通过 OMP_NUM_THREADS 环境变量的任何输入。你通常可以将 num_threads 排除在外,就可以解决这种简化的问题。

default(shared)

并行区域中变量的行为默认是shared,所以有没有理由在这里有 default(shared)

private(iam, i)

我想这是你的编码风格,但不是让 iami 私有(private),你可以只需在并行区域内声明它们,这将自动将它们设为私有(private)(考虑到您并没有真正在它之外使用它们,没有太多理由不这样做)。

#pragma omp for schedule(dynamic, 1)

另外正如 Hristo Iliev 提到的,使用 schedule(dynamic, 1) 特别针对这个问题集不是最好的主意,因为循环的每次迭代几乎不需要时间并且总的问题大小是固定的。

int grandTotal=0;
for (int j=0;j<nThread;j++) {
printf("Total=%d\n",total[j]);
grandTotal += total[j];
}

不一定是错误,但是你分配的total数组和最后的求和使用 OpenMP reduction 指令可以更好地完成。

double t2 = MPI_Wtime();

类似于您对 MPI_Init() 所做的,在您完成后调用 MPI_Wtime()调用的 MPI_Finalize() 未定义,应尽可能避免。

注意:如果您对 OpenMP 有点熟悉,this是一个很好的引用,基本上我在这里解释的关于 OpenMP 的所有内容都在那里。

顺便说一句,我必须指出您实际上并没有对 MPI 做任何事情,除了输出排名和通信大小。也就是说,所有的 MPI 任务不管任务的数量如何,每次都做固定数量的工作。既然有对于越来越多的 MPI 任务,每个任务的工作量没有减少,您不会期望有任何规模,你会吗? (注意:这实际上就是所谓的 Weak Scaling ,但由于您没有通过 MPI 进行通信,因此没有理由期望它不会完美缩放)。

这是用我提到的一些更改重写的代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>

#include <mpi.h>
#include <omp.h>

int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);

int world_size,
world_rank;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

int name_len;
char proc_name[MPI_MAX_PROCESSOR_NAME];
MPI_Get_processor_name(proc_name, &name_len);

MPI_Barrier(MPI_COMM_WORLD);
double t_start = MPI_Wtime();

// we need to scale the work per task by number of mpi threads,
// otherwise we actually do more work with the more tasks we have
const int n_iterations = 1e7 / world_size;

// actually we also need some dummy data to add so the compiler doesn't just
// optimize out the work loop with -O3 on
int data[16];
for (int i = 0; i < 16; ++i)
data[i] = rand() % 16;

// reduction(+:total) means that all threads will make a private
// copy of total at the beginning of this construct and then
// do a reduction operation with the + operator at the end (aka sum them
// all together)
unsigned int total = 0;
#pragma omp parallel reduction(+:total)
{
// both of these calls will execute properly since we
// are in an omp parallel region
int n_threads = omp_get_num_threads(),
thread_id = omp_get_thread_num();

// note: this code will only execute on a single thread (per mpi task)
#pragma omp master
{
printf("nThread = %d\n", n_threads);
}

#pragma omp for
for (int i = 0; i < n_iterations; i++)
total += data[i % 16];

printf("Hello from thread %d out of %d from process %d out of %d on %s\n",
thread_id, n_threads, world_rank, world_size, proc_name);
}

// do a reduction with MPI, otherwise the data we just calculated is useless
unsigned int grand_total;
MPI_Allreduce(&total, &grand_total, 1, MPI_UNSIGNED, MPI_SUM, MPI_COMM_WORLD);

// another barrier to make sure we wait for the slowest task
MPI_Barrier(MPI_COMM_WORLD);
double t_end = MPI_Wtime();

// output individual thread totals
printf("Thread total = %d\n", total);

// output results from a single thread
if (world_rank == 0)
{
printf("Grand Total = %d\n", grand_total);
printf("Time elapsed with MPI clock = %f\n", t_end - t_start);
}

MPI_Finalize();
return 0;
}

另外需要注意的是,添加了 schedule(dynamic, 1) 后,我的代码版本执行速度慢了 22 倍,只是为了向您展示它如何影响性能使用不当。

不幸的是,我对 PBS 不太熟悉,因为我使用的集群与 SLURM 一起运行,但一个示例 sbatch file对于在 3 个节点上运行的作业,在每个节点有两个 6 核处理器的系统上,可能看起来像这样:

#!/bin/bash
#SBATCH --job-name=TestOrSomething
#SBATCH --export=ALL
#SBATCH --partition=debug
#SBATCH --nodes=3
#SBATCH --ntasks-per-socket=1

# set 6 processes per thread here
export OMP_NUM_THREADS=6

# note that this will end up running 3 * (however many cpus
# are on a single node) mpi tasks, not just 3. Additionally
# the below line might use `mpirun` instead depending on the
# cluster
srun ./a.out

为了好玩,我还在集群上运行我的版本以测试 MPI 和 OMP 的缩放,并得到以下结果(注意对数缩放):

Scaling (time) for the example code. It's basically perfect.

如你所见,它基本上是完美的。实际上,1-16 是 1 个 MPI 任务和 1-16 个 OMP 线程,16-256 是 1-16 个 MPI 任务,每个任务有 16 个线程,因此您还可以看到 MPI 缩放和 OMP 缩放之间的行为没有变化.

关于c++ - 将 openMP 与 openMPI 混合时的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27296261/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com