簡體   English   中英

為什么我的OpemMP代碼性能比串行性能差?

[英]Why is my OpemMP code performance worst than serial?

我正在做一個簡單的Pi計算,在其中並行化生成隨機數和增加計數的循環。 串行(非OpenMP)代碼的性能優於OpenMP代碼。 這是我進行的一些測量。 這兩個代碼也在下面提供。

將串行代碼編譯為:gcc pi.c -O3

將OpenMP代碼編譯為:gcc pi_omp.c -O3 -fopenmp

可能是什么問題呢?

# Iterations = 60000000

Serial Time = 0.893912

OpenMP 1 Threads Time = 0.876654
OpenMP 2 Threads Time = 23.8537
OpenMP 4 Threads Time = 7.72415

串行碼:

/* Program to compute Pi using Monte Carlo methods */
/* from: http://www.dartmouth.edu/~rc/classes/soft_dev/C_simple_ex.html */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#define SEED 35791246

int main(int argc, char* argv)
{
  int niter=0;
  double x,y;
  int i;
  long count=0; /* # of points in the 1st quadrant of unit circle */
  double z;
  double pi;

  printf("Enter the number of iterations used to estimate pi: ");
  scanf("%d",&niter);

  /* initialize random numbers */
  srand(SEED);
  count=0;
  struct timeval start, end;
  gettimeofday(&start, NULL);
  for ( i=0; i<niter; i++) {
    x = (double)rand()/RAND_MAX;
    y = (double)rand()/RAND_MAX;
    z = x*x+y*y;
    if (z<=1) count++;
  }
  pi=(double)count/niter*4;

  gettimeofday(&end, NULL);
  double t2 = end.tv_sec + (end.tv_usec/1000000.0);
  double t1 = start.tv_sec + (start.tv_usec/1000000.0);

  printf("Time: %lg\n", t2 - t1);

  printf("# of trials= %d , estimate of pi is %lg \n",niter,pi);
  return 0;
}

OpenMP並行代碼:

#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#define SEED 35791246
/*
from: http://www.dartmouth.edu/~rc/classes/soft_dev/C_simple_ex.html
 */
#define CHUNKSIZE 500
int main(int argc, char *argv[]) {

  int chunk = CHUNKSIZE;
  int niter=0;
  double x,y;
  int i;
  long count=0; /* # of points in the 1st quadrant of unit circle */
  double z;
  double pi;

  int nthreads, tid;

  printf("Enter the number of iterations used to estimate pi: ");
  scanf("%d",&niter);

  /* initialize random numbers */
  srand(SEED);
  struct timeval start, end;

  gettimeofday(&start, NULL);
  #pragma omp parallel shared(chunk) private(tid,i,x,y,z) reduction(+:count)  
  {                                                                                                           
    /* Obtain and print thread id */
    tid = omp_get_thread_num();
    //printf("Hello World from thread = %d\n", tid);

    /* Only master thread does this */
    if (tid == 0)
    {
      nthreads = omp_get_num_threads();
      printf("Number of threads = %d\n", nthreads);
    }

    #pragma omp for schedule(dynamic,chunk)                                                                       
    for ( i=0; i<niter; i++) {                                                                              
      x = (double)rand()/RAND_MAX;                                                                          
      y = (double)rand()/RAND_MAX;                                                                          
      z = x*x+y*y;                                                                                          
      if (z<=1) count++;                                                                                    
    }                                                                                                       
  }                                                                                                           

  gettimeofday(&end, NULL);
  double t2 = end.tv_sec + (end.tv_usec/1000000.0);
  double t1 = start.tv_sec + (start.tv_usec/1000000.0);

  printf("Time: %lg\n", t2 - t1);

  pi=(double)count/niter*4;                                                                                   
  printf("# of trials= %d, threads used: %d, estimate of pi is %lg \n",niter,nthreads, pi);
  return 0;
}

在這種特殊情況下,由於openMP需要10K-100K周期來啟動循環,因此存在很多可能性,因此openMP的性能提升是不平凡的。

在此之后,我們還有另一個問題,即rand無法重新進入http://man7.org/linux/man-pages/man3/rand.3.html

因此,最有可能一次只能由一個線程調用rand,因此您的開放MP版本本質上是單線程的,因為您的循環幾乎沒有其他作用,每次調用rand都會產生額外的爭用開銷-因此,速度會急劇下降。

rand()不可重入。 它要么不能正常工作,崩潰,要么只能一次從一個線程調用。 像glibc這樣的庫通常會序列化或將TLS用於舊的非重入函數,而不是在多線程代碼中使用它們時使它們隨機崩潰。

試試可重入表格rand_r()

tid = omp_get_thread_num();
unsigned int seed = tid;
...
x = (double)rand_r(&seed)/RAND_MAX;

我想您會發現它更快。

注意我如何將種子設置為潮汐。 您可能會想,為什么不將種子初始化為SEED呢? 給定相同的種子, rand_r()將產生相同的數字序列。 如果每個線程使用相同系列的偽隨機數,那么它就會失去進行更多迭代的目的! 您必須讓每個線程使用不同的數字。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM