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

C 基准测试,顺序x并行程序。次线性加速比?,c,multithreading,performance,parallel-processing,benchmarking,C,Multithreading,Performance,Parallel Processing,Benchmarking,更新2。解决了的!这是内存问题。这里有一些关于它的长凳: 更新。我的目标是实现最佳吞吐量。我所有的结果都在这里 顺序结果: 平行结果*: multsoma_par1_vN,N确定每个线程访问数据的方式。 N:1-N位移,2-L1位移,3-L2位移,4-TAM/N位移 我很难弄明白为什么我的并行代码比顺序代码运行得稍微快一点 我基本上是通过一个类型(int/float/double)的大数组(10^8个元素)循环并应用计算:a=a*常量+B。其中a和B是相同大小的数组 顺序代码只执行单个函数

更新2。解决了的!这是内存问题。这里有一些关于它的长凳:

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

顺序结果:

平行结果*:

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

我很难弄明白为什么我的并行代码比顺序代码运行得稍微快一点

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

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

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

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

问题是,在顺序代码下,我可以得到2.00CPE,而在并行时,我的最佳性能是1.84CPE。我知道通过创建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(我的最佳计时),每个线程的CPE为7.0~7.5

使用比可用内核更多的线程(4),我仍然获得2.0以下的CPE,但不超过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;   
}
#包括
#包括
#包括
#包括
#包括
typedef联合{
无符号长int64;
结构{unsigned int lo,hi;}int32;
}tsc_计数器;
#定义RDTSC(cpu\U c)\
__asm\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\
“=a”((cpu_c).int32.lo)\
“=d”((cpu_c.int32.hi))
#定义CNST 5
#定义第4行
#定义L1_大小8096
#定义L2_大小72512
typedef int data_t;
数据_t*A;
数据_t*B;
INTTAM;
双平均螺纹;
tsc_计数器线程_t0[n线程]、线程_t1[n线程];
struct timeval thread_sec0[NTHREADS],thread_sec1[NTHREADS];
无效填充物(内部tam){
int i;

对于(i=0;i在看到完整的代码后,我非常同意注释中对@nosid的猜测:因为计算操作与内存负载的比率很低,数据(如果我没有弄错的话,大约800M)不适合缓存,内存带宽可能是限制因素。到主内存的链接共享给处理器中的所有内核,因此当其带宽饱和时,所有内存操作开始暂停并花费更长的时间;因此CPE增加

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

avg_thread_CPE+=CPE;
当您将在不同线程上计算的CPE值汇总到单个全局变量时,无需任何同步


下面我留下了我最初回答的部分,包括评论中提到的“第一句话”。我仍然认为CPE的定义是一个元素上的操作所占用的时钟数是正确的

您不应该期望每个元素的时钟(CPE)指标减少 由于使用了多个线程。根据定义,它是一个线程的速度 平均而言,只处理一个数据项。线程有助于更快地处理所有数据(通过在不同数据库上同时处理) 内核),因此经过的挂钟时间,即执行 整个计划,预计将减少


int*id=(int*)arg;-这看起来很可疑。通常,线程索引是通过值传递的,而不是通过指针传递的。如何创建线程?@Alexey Kukanov这不是问题,它可以工作。这个函数是pthread_create调用使用的函数,因此我必须将参数作为void*。我怀疑我见过几次的错误:线程是在循环中创建的,一个d循环索引的地址作为参数传递给所有线程。它会导致多个数据争用和未定义的行为。如果您确定这不是问题,并且每个线程都会收到一个不同变量的地址,很好。
int size=tam/NTHREADS;
-如果
tam
是您的元素总数,那么该除法m我可以休息一下。你正在做大量的内存操作,基本上没有计算。我想,内存带宽是这种情况下的限制因素。也许可以给你更多的见解。我不同意你的第一句话。每个线程CPE也不应该减少。你是对的,avg_thread_CPE变量在任何时候都不同步随时(我必须解决这个问题)。但不要认为这是问题所在,因为我得到的这个值是所有线程CPE的总和,并且avg CPE是可靠的。这个平均值是=avg_thread_CPE/NTHREADS。我的主要CPE是通过另一个单独调用RDT来计算的,该调用包含所有pthread创建。我只通过一个线程的一个RDTSC调用获得3.8 CPE。它们的总和是7.6(2 threa)