更新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)指标减少
由于使用多个线程。根据定义,这是
平均处理单个数据项。线程化有助于更快地处理所有数据(通过同时处理不同的数据
核数),因此经过的挂钟时间,即执行
整个程序,应该减少。