基准测试,顺序x并行程序。次线性加速?

时间:2014-04-30 04:37:20

标签: c multithreading performance parallel-processing benchmarking

UPDATE2。解决了!这是内存问题。有人在这里讨论它: http://dontpad.com/bench_mem

更新。我的目标是实现最佳吞吐量。我的所有结果都在这里。

顺序结果: https://docs.google.com/spreadsheet/ccc?key=0AjKHxPB2qgJXdE8yQVNHRkRiQ2VzeElIRWwxMWtRcVE&usp=sharing

平行结果*: https://docs.google.com/spreadsheet/ccc?key=0AjKHxPB2qgJXdEhTb2plT09PNEs3ajBvWUlVaWt0ZUE&usp=sharing

multsoma_par1_vN,N确定每个线程如何访问数据。 N:1 - NTHREADS位移,2 - L1位移,3 - L2位移,4 - TAM / NTHREADS

我很难弄清楚为什么我的并行代码运行速度比顺序代码快一点。

我基本上做的是循环一个类型的大数组(10 ^ 8个元素)(int / float / double)并应用计算:A = A * CONSTANT + B.其中A和B是数组相同的大小。

顺序代码只执行单个函数调用。 并行版本创建pthreads并使用与启动函数相同的功能。

我使用gettimeofday(),RDTSC()以及最近的getrusage()来测量时间。我的主要结果由每个元素的时钟(CPE)表示。

我的处理器是i5-3570K。 4个核心,没有超线程。

问题在于我可以在顺序代码下达到2.00 CPE,并且在并行时我的最佳性能是1.84 CPE。我知道我通过创建pthreads并调用更多的计时例程来获得开销,但我不认为这是没有获得更好的时间的原因。 我测量了每个线程的CPE,并用1,2,3和4个线程执行程序。当只创建一个线程时,我得到预期的结果CPE大约2.00(+一些开销用毫秒表示,但总体CPE根本不受影响)。 当使用2个或更多线程运行时,主CPE减少,但每个线程CPE增加。 2个线程我得到主要CPE大约1.9和每个线程到3.8(为什么这不是2.0?!) 3线和4线也是如此。 4个线程我得到主要CPE大约1.85(我的最佳时机)和每个线程7.0~7.5 CPE。

使用多个线程而不是可用核心(4)我仍然将CPE低于2.0但不高于1.85(由于开销而大多数时候更高)。

我怀疑上下文切换可能是限制因素。当使用2个线程运行时,我可以计算从每个线程切换5到10个非自愿的上下文... 但我对此不太确定。那些似乎很少的上下文切换足以使我的CPE几乎翻倍吗?我期待使用我所有的CPU核心至少获得1.00 CPE。

我进一步研究了这个并分析了这个函数的汇编代码。它们是相同的,除了一些额外的移位并在函数的最开始添加(4条指令)并且它们没有循环。

如果你想看一些代码:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include <cpuid.h>

typedef union{
   unsigned long long int64;
   struct {unsigned int lo, hi;} int32;
} tsc_counter;

#define RDTSC(cpu_c)                 \
  __asm__ __volatile__ ("rdtsc" :    \
  "=a" ((cpu_c).int32.lo),           \
  "=d" ((cpu_c).int32.hi) )

#define CNST 5
#define NTHREADS 4
#define L1_SIZE 8096
#define L2_SIZE 72512

typedef int data_t;

data_t * A;
data_t * B;

int tam;
double avg_thread_CPE;
tsc_counter thread_t0[NTHREADS], thread_t1[NTHREADS];
struct timeval thread_sec0[NTHREADS], thread_sec1[NTHREADS];

void fillA_B(int tam){
    int i;
    for (i=0;i<tam;i++){
        A[i]=2; B[i]=2;
    }
    return;
}

void* multsoma_par4_v4(void *arg){
    int w;
    int i,j;
    int *id = (int *) arg;
    int limit = tam-14;
    int size = tam/NTHREADS;
    int tam2 = ((*id+1)*size);
    int limit2 = tam2-14;

    gettimeofday(&thread_sec0[*id],NULL);
    RDTSC(thread_t0[*id]);

    //Mult e Soma
    for (i=(*id)*size;i<limit2 && i<limit;i+=15){
          A[i] = A[i] * CNST + B[i];
          A[i+1] = A[i+1] * CNST + B[i+1];
          A[i+2] = A[i+2] * CNST + B[i+2];
          A[i+3] = A[i+3] * CNST + B[i+3];
        A[i+4] = A[i+4] * CNST + B[i+4];
        A[i+5] = A[i+5] * CNST + B[i+5];
        A[i+6] = A[i+6] * CNST + B[i+6];
        A[i+7] = A[i+7] * CNST + B[i+7];
        A[i+8] = A[i+8] * CNST + B[i+8];
        A[i+9] = A[i+9] * CNST + B[i+9];
        A[i+10] = A[i+10] * CNST + B[i+10];
        A[i+11] = A[i+11] * CNST + B[i+11];
        A[i+12] = A[i+12] * CNST + B[i+12];
        A[i+13] = A[i+13] * CNST + B[i+13];
        A[i+14] = A[i+14] * CNST + B[i+14];
    }

    for (; i<tam2 && i<tam; i++)
        A[i] = A[i] * CNST + B[i];

    RDTSC(thread_t1[*id]);
    gettimeofday(&thread_sec1[*id],NULL);

    double CPE, elapsed_time;

    CPE = ((double)(thread_t1[*id].int64-thread_t0[*id].int64))/((double)(size)); 

    elapsed_time = (double)(thread_sec1[*id].tv_sec-thread_sec0[*id].tv_sec)*1000;
    elapsed_time+= (double)(thread_sec1[*id].tv_usec - thread_sec0[*id].tv_usec)/1000;  
    //printf("Thread %d workset - %d\n",*id,size);
    //printf("CPE Thread %d - %lf\n",*id, CPE);    
    //printf("Time Thread %d - %lf\n",*id, elapsed_time/1000);
    avg_thread_CPE+=CPE;


    free(arg);
    pthread_exit(NULL);
}


void imprime(int tam){
    int i;
    int ans = 12;
    for (i=0;i<tam;i++){
        //printf("%d ",A[i]);
        //checking...
        if (A[i]!=ans) printf("WA!!\n");
    }
    printf("\n");
    return;
}

int main(int argc, char *argv[]){
    tsc_counter t0,t1;
    struct timeval sec0,sec1;
    pthread_t thread[NTHREADS];

    double CPE;
    double elapsed_time;      

    int i;
    int* id;

    tam = atoi(argv[1]);  

    A = (data_t*) malloc (tam*sizeof(data_t));
    B = (data_t*) malloc (tam*sizeof(data_t));

    fillA_B(tam);
    avg_thread_CPE = 0;

    //Start Computing... 
     gettimeofday(&sec0,NULL);
     RDTSC(t0);                                  //Time Stamp 0

    for (i=0;i<NTHREADS;i++){     
        id = (int*) malloc(sizeof(int));   
        *id = i;
        if (pthread_create(&thread[i], NULL, multsoma_par4_v4, (void*)id)) {
             printf("--ERRO: pthread_create()\n"); exit(-1);
          }

    } 

    for (i=0; i<NTHREADS; i++) {
         if (pthread_join(thread[i], NULL)) {
              printf("--ERRO: pthread_join() \n"); exit(-1); 
         } 
    }


     RDTSC(t1);                                  //Time Stamp 1
     gettimeofday(&sec1,NULL);
    //End Computing...

    imprime(tam);

    CPE = ((double)(t1.int64-t0.int64))/((double)(tam));        //diferenca entre Time_Stamps/repeticoes

     elapsed_time = (double)(sec1.tv_sec-sec0.tv_sec)*1000;
     elapsed_time+= (double)(sec1.tv_usec - sec0.tv_usec)/1000;

     printf("Main CPE: %lf\n",CPE);
     printf("Avg Thread CPE: %lf\n",avg_thread_CPE/NTHREADS);
     printf("Time: %lf\n",elapsed_time/1000);
     free(A); free(B);

    return 0;   
}

我感谢任何帮助。

1 个答案:

答案 0 :(得分:0)

在看完完整的代码之后,我更赞同评论中对@nosid的猜测:因为计算操作与内存负载的比率很低,而且数据(如果我没有记错的话大约800M)不要适合缓存,内存带宽可能是限制因素。主存储器的链接共享给处理器中的所有内核,因此当其带宽饱和时,所有内存操作都会停止并占用更长的时间;因此CPE增加。

此外,代码中的以下位置是数据竞争:

avg_thread_CPE+=CPE;

将不同线程上计算的CPE值汇总到单个全局变量而不进行任何同步。


下面我留下了我最初答案的部分内容,包括&#34;第一个陈述&#34;在评论中提到。我仍然觉得它是正确的,因为CPE的定义是操作在单个元素上占用的时钟数。

  

您不应期望每个元素的时钟(CPE)指标会降低   由于使用了多个线程。根据定义,它的速度有多快   平均处理单个数据项。线程有助于更快地处理所有数据(通过同时处理不同的数据)   核心),所以经过的挂钟时间,即执行时间   整个计划,应该会减少。