2014-12-04 4 views
4

for 루프를 사용하여 openMP를 사용하여 병렬화 된 코드가 있습니다. 지금은 여러 번 기능을 반복하고 MPI를 사용하여 기계 클러스터에 제출하여 인트라 노드를 계속 유지하면서 OpenMP로 유지하려고했습니다.openMPI와 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/빈/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 노드 = 1 : ppn = 12로 시도했지만 동일한 결과를 얻습니다.

코어를 절반으로 사용하면 프로그램이 실제로 더 빠릅니다 (속도가 두 배 빨라짐). 코어 수를 줄이면 ncpus와 OMP_NUM_THREADS가 모두 변경됩니다. 나는 실제 작업을 증가 시키려고 노력했다. (코드에서 여기에 표시된 10^7 대신 10^10을 추가했다.) printf 명령문을 제거해 보았습니다. printf 명령문을 제거해 봤는데, 어떻게 든 느려지는지 계속 궁금해하고 있습니다. 여전히 같은 문제가 있습니다. 위로는 100 %에 가까운 모든 CPU (ncpus에서 설정된대로)를 사용하고 있음을 보여줍니다. -np = 2를 사용하여 제출하면 두 대의 컴퓨터에서 아름답게 병렬 처리되므로 MPI가 예상대로 작동하지만 OpenMP가 손상된 것 같습니다.

아이디어가 없으므로 시도해 볼 수있는 모든 것. 내가 도대체 ​​뭘 잘못하고있는 겁니까?

+3

나는 당신이 어떤 속도 향상을 전혀 얻지 못해서 놀랍다. 'schedule (dynamic, 1)'은이 경우 루프 스케쥴을 최악으로 선택할 수 있으며, 다중 쓰레드로부터 어레이의 이웃 요소에 끊임없이 쓰는 것은 잘못된 공유로 인해 많은 수의 캐시가 파손되게 만듭니다. 12 코어보다 6 코어에서 더 빨리 실행되는 이유입니다 (참고 : x86의 한 캐시 라인은 16 개의 int에 해당합니다). –

+1

또한'num_threads (omp_get_num_procs())'를 사용하면 병렬 영역이 논리적 CPU 수 (즉, 코어, 결국 하이퍼 스레딩을하는 두 시간)만큼 많은 스레드에서 항상 실행되므로'OMP_NUM_THREADS'를 효과적으로 쓸 수 없게됩니다. 'top-'에'-map-by node : pe = X'는 MPI 프로세스를 노드의 첫 번째 X CPU에 바인드하기 때문에 실제로 이것을 'top'으로 보지 않을 것이다. –

+0

각 스레드는 동일한 배열이 아닌 자체 배열에 쓰여 있어야합니다. 이것은 내가하고 싶은 일이며, 논리를 놓친 곳을 지적하십시오. 논리 CPU 수만큼 배열을 만듭니다 (omp_get_num_procs()). 각 스레드의 스레드는 해당 스레드에 특정한 배열 (이 경우 [총] [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) 

당신이 흐리 일리 예프 언급했듯이, 효과적으로 OMP_NUM_THREADS는 환경 변수를 통해 임의의 입력을 무시하는 여기에 nThreadnum_threads 세트. 보통은 으로두고 num_threads으로두면 간단한 문제 일 수 있습니다.

default(shared) 

병렬 지역 변수의 동작은 기본 shared입니다, 그래서 여기에 default(shared)을 할 이유는 없습니다.

private(iam, i) 

내가 추측이 대신 iami 개인을 만드는, 당신의 코딩 스타일, 당신은 단 자동 그들을 비공개 것이다, 병렬 지역 내에서 선언 (그리고 당신이 정말로하지 않는 사용을 고려 수 그것들의 바깥쪽에는 그렇게 할 이유가별로 없습니다). 루프의 각 반복은 사실상 시간 지지 않습니다 및 전체 문제의 크기가 고정되어 있기 때문에

#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]; 
} 

반드시 그렇지는 오류하지만 더 나은 OpenMP를 reduction 지시어를 사용하여 수행됩니다 마지막에 total 배열과 요약의 당신의 할당. 당신이 MPI_Finalize()이 정의되지 라고했습니다, 그리고 가능하면 피해야한다 후 MPI_Wtime()를 호출하면 MPI_Init()했던 것과 유사

double t2 = MPI_Wtime(); 

.

참고 : OpenMP가 무엇인지 잘 알고 있다면 this 을 참고할 수 있습니다. 기본적으로 OpenMP에 대한 설명은 여기에 있습니다.

그런데도 MPI로는 아무 것도하지 않았 음을 알아 두어야합니다. 출력과 순위 크기 외에도. 즉, 모든 MPI 작업 은 숫자에 관계없이 각각 고정 된 작업을 수행합니다.. MPI 작업 수가 증가함에 따라 작업 당 작업량이 감소하지 않기 때문에 에 스케일링이있을 것으로 기대하지 않습니까? (참고 : 이것은 실제로 Weak Scaling이라고 불리는 것이지만, MPI를 통한 통신이 없기 때문에 이 완벽하게 확장되지 않을 것으로 기대할 이유가 없습니다.) 여기

이 코드는 내가 언급 한 몇 가지 변화와 함께 다시 것 : schedule(dynamic, 1)와 느린

#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 시간는 어떻게 할 수있는 당신을 보여주기 위해 추가 잘못 사용될 경우 성능에 영향을줍니다.

불행하게도 나는 SLURM으로 실행을 사용하여 클러스터하지만, 노드 당 두 개의 6 코어 프로세서 시스템에서, 3 개 노드에서 실행중인 작업에 대한 예를 들어 sbatch file로, 같이 보일 수도 PBS 너무 익숙하지 해요 이 : 재미에 대한

#!/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.

,536,913,632 10

보시다시피, 기본적으로 완벽합니다.실제로 1-16은 1-16 개의 OMP 스레드가있는 1 개의 MPI 작업이고 16-256은 1 개의 작업에 16 개의 스레드가있는 1-16 개의 MPI 작업이므로 MPI 확장과 OMP 확장 간의 동작에는 변화가 없음을 볼 수도 있습니다 .

+0

감사합니다. 분명히 이것은 장난감 문제이며 iam과 i를 openmp 루프 외부에서 사용하는 다른 응용 프로그램에 대해서는 private (iam, i)가 필요하며 많은 수의 항목을 추가하는 것보다 훨씬 복잡한 작업을 수행합니다. omp 병렬 감소를 사용하지 않는 것과 비슷한 이유. 그러나 나는 다른 제안을 시도 할 것이다. – Anu

+0

@Anu 제안 사항 중 어느 것도 실제 문제를 해결하지 못하면 가능한 한 실제 코드를 간소화하고 다른 질문을 게시하려고합니다. 장난감 문제에서 문제를 재현하기가 어렵습니다. – cartographer