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

时间:2014-12-04 14:15:20

标签: c++ openmp openmpi

我有一些使用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已损坏

现在出于想法,我可以尝试任何事情。我做错了什么?

1 个答案:

答案 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)

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

#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的缩放比例,并得到以下内容(请注意日志比例):

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

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