是否可以在具有多核AMD CPU的计算机上加速C中的阵列操作?我列出了下面的代码。
UDP数据包每512ns到达一次,下面数组中的计算(其中数据在else {}循环中累积)需要超过512ns。当数据包以1微秒的间隔到达时,此循环会保持运行。我的问题:可以通过多线程进行数组累积,从而加快计算速度吗?目前该计划' top'表明当数据包以512ns的间隔到达时,代码使用100%的CPU。提前感谢任何意见/建议。
#define NC 64
#define NP 4
void* get_next_buf(mystr_t* str, uint64_t* size)
{
char buf0[8500];
long long pktnum, *ptr;
int i,j,J, offset ;
ssize_t recsize;
socklen_t fromlen;
int pktdiff;
recsize = recvfrom(str->sock, (void *)buf0, 8224, 0, (struct sockaddr *)&str->sa, &fromlen);
if (recsize < 0) {
fprintf(stderr,"error reading udp packet\n");
return 0;
}
/* clear the array for accumulate*/
memset(str->data, 0, 2*NCHAN*NPOL*sizeof(short));
/* swap bytes to extract packet counter*/
ptr = (long long *)buf0;
pktnum=BSWAP_64( *ptr ) & 0x00ffffffffffffff;
// got one packet of data. If a pakcet was missed, return array with zeroes
pktdiff = pktnum - str->prev_pkt_cnt;
if ( pktdiff != 1){
str->bad_pkt_cnt++;
fprintf (stderr,"%d+",pktdiff);
str->prev_pkt_cnt = pktnum;
*size = 2*sizeof(short)*NC*NP;
return (void*) str->data;
}
//packet arrived in correct order, accumulate and return the array
else {
J = 8192/(NC*NP);
for (i=0;i<J;i++){
for (j=0;j<NC;j=j++){
offset = i*NC*NP;
((short *)str->data)[j] += (short)(buf0[8+j+offset]);
((short *)str->data)[j+64] += (short)(buf0[8+64+j+offset]);
((short *)str->data)[j+128] += (short)(buf0[8+128+j+offset]);
((short *)str->data)[j+192] += (short)(buf0[8+192+j+offset]);
}
}
*size = sizeof(short)*NC*NP;
str->prev_pkt_cnt = pktnum;
/*return the acquired data buffer */
return (void*) str->data;
}
}
答案 0 :(得分:3)
这是相当严密的代码,但是如果我迫不及待地让它跑得快一点,而且找不到比我更专业的人,我会尝试:
在j循环中重复四次(short *)str-&gt; data ,而不是 short * 设置为的指针> STR-&GT;数据。保存deref。无论如何,任何中途不错的编译器应该像这样优化,但有时编译器并不完美。
同样在j循环中,(短)转换可能占用一个操作码 - 有符号或零扩展一个字节值为16位。如果buf0声明为short而不是char,则保存一个时钟周期。 buf0 []的类型在其他地方似乎并不重要,无论如何都要转换为其他类型。索引必须进行调整。小心不要涉及任何额外的操作,否则你没有任何改进。
处理并行添加短期内注 - 可能是好的&#39;从20世纪90年代起,MMX可以提供帮助。或者更好的是,最新的SIMD操作码。那些喜欢处理相邻数据的人,而你的展开循环在任何一次迭代中都不会对相邻的部分起作用。也许它可以通过其他方式展开。同样,好的编译器应该注意到可以使用SIMD操作的情况,但这可能需要一些特殊的命令行标志,并且可能由于循环的方式而不会发生。重新排列循环以在最里面的[j],[j + 1],[j + 2],[j + 3]上工作,也许你很幸运。
在一个巨大的阵列上工作的两个或多个线程通常是加快速度的好方法,但是巨大的线程必须比典型的内存缓存页面或虚拟内存页面大,所以线程不需要写访问权限相同的东西。我没有达到该级别的最新架构,但我非常确定只有8000个字节的数组不是&#34; t&#34;巨大的&#34;在需要的意义上。
如果线程可以提供帮助,那么方法是让两个交替,轮流到达时轮流。一个完成而另一个开始。好吧,如果开始新数据包的人不需要其他未完成数据包的结果。但是,我并不是这样的大师。
答案 1 :(得分:1)
您在每次迭代时都有很多计算,可以在外部完成,或者定义为常量NC*NP
甚至offset = i*NC*NP;
,它们不应该在for j
循环中它不依赖于j。
当你可以使用变量增量时,8+j+offset
也会多次完成。
你的其他人应该是这样的:
// the following are defined on top of your program
// #define NCNP NC*NP
// #define J 8192/(NCNP);
// #define SIZE sizeof(short)*NCNP
offset=8;
for (i=0;i<J;i++){
offset += NCNP;
jo=offset;
for (j=0;j<NC;j++){
((short *)str->data)[j] += (short)(buf0[jo]);
((short *)str->data)[j+64] += (short)(buf0[64+jo]);
((short *)str->data)[j+128] += (short)(buf0[128+jo]);
((short *)str->data)[j+192] += (short)(buf0[192+jo]);
jo++;
}
}
*size = SIZE;
str->prev_pkt_cnt = pktnum;
/*return the acquired data buffer */
return (void*) str->data;
答案 2 :(得分:0)
上面的一些提示,加上代码的更改有助于代码执行而不会丢失任何数据包。来自@Adam的最后一个有一些效果 - 谢谢Adam!
我输入的第二个更改是,每次调用get_next_buf()函数时接收两个数据包,如下所示:
recsize = recvfrom(str->sock, (void *)buf0, 8224, 0, (struct sockaddr *)&str->sa, &fromlen);
recsize = recvfrom(str->sock, (void *)buf1, 8224, 0, (struct sockaddr *)&str->sa, &fromlen);
然后,来自buf0和buf1的数据在str-> data []中累积如下:
J = 8192/(NCNP);
offset = 8;
for (i=0;i<J;++i){
offset += NCNP;
jo = offset;
for (j=0;j<NC;++j){
if (i==0 && j==0) memset(udp2db->data,0,NCNP*sizeof(short));
((short *)udp2db->data)[j] += (short)(buf0[jo]) + (short)(buf1[jo]);
((short *)udp2db->data)[j+64] += (short)(buf0[64+jo]) + (short)(buf1[64+jo]) ;
((short *)udp2db->data)[j+128] += (short)(buf0[128+jo]) + (short)(buf1[128+jo]);
((short *)udp2db->data)[j+192] += (short)(buf0[192+jo]) + (short)(buf1[192+jo]);
jo++;
}
}
由于以上似乎永远不会超过83%的CPU负载,这应该没问题。另请注意,我可以从两个连续的数据包中添加数据,因为它们传递得更快。我希望软件累积的主要原因是为了避免使用8位累加器的硬件溢出。再次感谢所有人的建议!