我有一些使用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;
}
我使用-fopenmp标志编译openmpi-1.8 / bin / mpic ++。这是我的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语句,想知道它们是否以某种方式减慢了速度,仍然有同样的问题。 Top显示我使用的所有CPU(在ncpus中设置)接近100%。如果我使用-np = 2提交,它会在两台机器上并行化,因此MPI似乎按预期工作,但openMP已损坏
现在出于想法,我可以尝试任何事情。我做错了什么?
答案 0 :(得分:3)
我不想这么说,但是有很多错误,你应该只是熟悉一下 使用OpenMP和MPI更多地了解自己。不过,我会试着通过你的代码 并指出我看到的错误。
double t1 = MPI_Wtime();
开始:在MPI_Wtime()
之前调用MPI_Init()
未定义。我还要补充说,如果你
想要用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)
我想这是你的编码风格,但不是私密的iam
和i
,你可以
只需在并行区域内声明它们,这将自动将它们设为私有
(并且考虑到你并没有真正使用它们,没有太多理由不这样做。)
#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
一起运行,但是对于在3个节点上运行的作业的示例sbatch file,在具有两个的系统上每个节点的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的缩放比例,并得到以下内容(请注意日志比例):
正如你所看到的,它基本上是完美的。实际上,1-16是具有1-16个OMP线程的1 MPI任务,16-256是1-16个MPI任务,每个任务有16个线程,因此您还可以看到MPI缩放和OMP缩放之间的行为没有变化