2014-12-04 2 views
4

У меня есть код, который распараллеливается с использованием openMP (в цикле for). Я хотел бы повторить эту функцию несколько раз и использовать MPI для отправки в кластер машин, сохраняя, чтобы содержимое внутри узла все еще было открытым.Странное поведение при смешивании openMP с openMPI

Когда я использую только 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/КОМП ++, используя -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 узлами = 1: ррп = 12, получаем те же результаты.

При использовании половины сердечников программа работает быстрее (в два раза быстрее!). Когда я уменьшаю количество ядер, я меняю как ncpus, так и OMP_NUM_THREADS. Я попытался увеличить фактическую работу (добавив 10 код 10 вместо 10^7, показанный здесь в коде). Я попытался удалить инструкции printf, задаваясь вопросом, не замедлили ли они что-то, все еще имеют ту же проблему. Топ показывает, что я использую все процессоры (как установлено в ncpus), близкие к 100%. Если я отправлю с помощью -np = 2, он прекрасно распараллеливается на двух машинах, поэтому MPI работает нормально, но openMP не работает

Из идей теперь все, что я могу попробовать. Что я делаю не так?

+3

Я удивлен, что вы получаете любое ускорение вообще. 'schedule (dynamic, 1)' самый худший выбор графика цикла в этом случае, и постоянная запись в соседние элементы массива из нескольких потоков приводит к большому сбою кеша из-за ложного обмена. Вероятно, поэтому он работает быстрее на 6 ядрах, чем на 12 (обратите внимание: одна строка кэша на x86 подходит для 16 'int'-egers). –

+1

Кроме того, наличие 'num_threads (omp_get_num_procs())' эффективно делает установку «OMP_NUM_THREADS» бесполезной, так как ваши параллельные области будут всегда работать с таким количеством потоков, сколько количество логических процессоров (например, ядер, в конечном итоге раз два с гиперпотоком). На самом деле вы не увидите этого в 'top', поскольку' --map-by node: pe = X' связывает ваш MPI-процесс с первыми X-процессорами на узле. –

+0

Каждый поток должен записываться в собственный массив, а не в тот же массив. Это то, что я хочу сделать, укажите, где я пропущу логику. Создайте столько массивов, сколько логических процессоров (omp_get_num_procs()). В каждом потоке каждая работа (добавление в этом случае) выполняется в массиве, специфичном для этого потока (total [ithread]). – Anu

ответ

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 здесь, который, как упоминалось Христо Илиев, эффективно игнорирует вход через переменную в OMP_NUM_THREADS среды. Обычно вы можете просто покинуть num_threads и быть в порядке для такого рода упрощенной проблемы.

default(shared) 

Поведение переменных в параллельной области по умолчанию shared, так что нет никаких оснований иметь default(shared) здесь.

private(iam, i) 

Я предполагаю, что это ваш стиль кодирования, но вместо того, чтобы iam и i частного, вы могли бы просто объявить их в параллельной области, которая будет автоматически сделать их частными (и учитывая, что вы на самом деле не использовать их вне его, не так много причин).

#pragma omp for schedule(dynamic, 1) 

Кроме того, как упоминалось Христо Илиев, используя 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; 
} 

Еще одна вещь отметить, моя версия кода исполненные 22 раз медленнее с schedule(dynamic, 1) добавил, чтобы показать вам, как это может при неправильном использовании.

К сожалению, я не слишком хорошо знаком с 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 ,

+0

Спасибо, я попробую это. Ясно, что это проблема с игрушкой, и мне нужен частный (iam, i) для другого приложения, которое использует iam и i за пределами цикла openmp и делает вещи намного сложнее, чем сложение кучки чисел. Подобная причина не связана с параллельным сокращением omp. Но я попробую другие предложения. – Anu

+0

@Anu Если ни один из предложений не работает над реальной проблемой, я попытаюсь как можно больше упростить реальный код и задать другой вопрос, иногда просто сложно воспроизвести проблему в игрушечной проблеме. – cartographer