更新2。解决了!这是内存问题。在这里进行一些替补:
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

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

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

顺序代码仅执行一个函数调用。
并行版本创建pthread,并使用与启动函数相同的函数。

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

我的处理器是i5-3570K。 4核,无超线程。

问题是在顺序代码下我可以达到2.00 CPE,而并行运行时,我的最佳性能是1.84 CPE。我知道通过创建pthread和调用更多计时例程会增加开销,但是我不认为这不是无法获得更好计时的原因。
我确实测量了每个线程的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;
}


感谢您的帮助。

最佳答案

看完完整的代码后,我宁愿在注释中使用@nosid的猜测:由于计算操作与内存负载的比率很低,并且数据(如果我没记错的话,约为800M)不适合放入缓存,因此内存带宽可能是限制因素。与主内存的链接由处理器中的所有内核共享,因此当其带宽达到饱和时,所有内存操作将开始停顿并花费更长的时间。因此CPE会增加。

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

avg_thread_CPE+=CPE;


当您将在不同线程上计算的CPE值汇总为单个全局变量时,无需进行任何同步。



在下面,我留下了我最初的回答的一部分,包括评论中提到的“第一条陈述”。对于CPE的定义,我仍然认为它是正确的,因为CPE定义为单个元素上的操作占用的时钟数。


  您不应期望每元素时钟(CPE)指标减少
  由于使用多个线程。根据定义,这是
  平均处理单个数据项。线程化有助于更快地处理所有数据(通过同时处理不同的数据
  核数),因此经过的挂钟时间,即执行
  整个程序,应该减少。

10-08 04:26