SIMD值得吗?有更好的选择吗?
Posted
技术标签:
【中文标题】SIMD值得吗?有更好的选择吗?【英文标题】:Is SIMD Worth It? Is there a better option? 【发布时间】:2010-07-18 18:50:48 【问题描述】:我有一些运行良好的代码,但我想让它运行得更好。我遇到的主要问题是它需要有一个嵌套的 for 循环。外层用于迭代(必须连续发生),内层用于考虑中的每个点粒子。我知道对于外部我无能为力,但我想知道是否有一种优化方法,例如:
void collide(particle particles[], box boxes[],
double boxShiftX, double boxShiftY) /**/
int i;
double nX;
double nY;
int boxnum;
for(i=0;i<PART_COUNT;i++)
boxnum = ((((int)(particles[i].sX+boxShiftX))/BOX_SIZE)%BWIDTH+
BWIDTH*((((int)(particles[i].sY+boxShiftY))/BOX_SIZE)%BHEIGHT));
//copied and pasted the macro which is why it's kinda odd looking
particles[i].vX -= boxes[boxnum].mX;
particles[i].vY -= boxes[boxnum].mY;
if(boxes[boxnum].rotDir == 1)
nX = particles[i].vX*Wxx+particles[i].vY*Wxy;
nY = particles[i].vX*Wyx+particles[i].vY*Wyy;
else //to make it randomly pick a rot. direction
nX = particles[i].vX*Wxx-particles[i].vY*Wxy;
nY = -particles[i].vX*Wyx+particles[i].vY*Wyy;
particles[i].vX = nX + boxes[boxnum].mX;
particles[i].vY = nY + boxes[boxnum].mY;
/**/
我查看过 SIMD,虽然我找不到太多关于它的信息,而且我不完全确定正确提取和打包数据所需的处理是否值得执行一半的指令,因为显然一次只能使用两个双打。
我尝试使用 shm 和 pthread_barrier 将其分解为多个线程(以同步不同的阶段,上面的代码就是其中之一),但这只是让它变慢了。
我当前的代码运行得很快;每 10M 粒子*迭代大约需要 1 秒,从 gprof 可以看出,我 30% 的时间都花在了这个函数上(5000 次调用;PART_COUNT=8192 个粒子花费了 1.8 秒)。我不担心小的、恒定时间的事情,只是上次 512K 粒子 * 50K 迭代 * 1000 次实验花了一周多的时间。
我想我的问题是,是否有任何方法可以比循环遍历它们更有效地处理这些长向量。我觉得应该有,但我找不到。
【问题讨论】:
【参考方案1】:我不确定 SIMD 能从中受益多少;内部循环非常小而且简单,所以我猜(只是通过观察)你可能比其他任何东西都更受内存限制。考虑到这一点,我会尝试重写循环的主要部分,以免超出需要的范围接触粒子数组:
const double temp_vX = particles[i].vX - boxes[boxnum].mX;
const double temp_vY = particles[i].vY - boxes[boxnum].mY;
if(boxes[boxnum].rotDir == 1)
nX = temp_vX*Wxx+temp_vY*Wxy;
nY = temp_vX*Wyx+temp_vY*Wyy;
else
//to make it randomly pick a rot. direction
nX = temp_vX*Wxx-temp_vY*Wxy;
nY = -temp_vX*Wyx+temp_vY*Wyy;
particles[i].vX = nX;
particles[i].vY = nY;
这有一点潜在的副作用,即最后不进行额外的添加。
另一个潜在的加速是在粒子数组上使用__restrict
,以便编译器可以更好地优化写入速度。此外,如果 Wxx 等是全局变量,它们可能必须每次通过循环重新加载,而不是可能存储在寄存器中;使用 __restrict
也会有所帮助。
由于您按顺序访问粒子,因此您可以尝试提前预取一些粒子(例如 GCC 上的 __builtin_prefetch
)以减少缓存未命中。由于您以不可预知的顺序访问它们,因此在盒子上预取有点困难;你可以尝试类似的东西
int nextBoxnum = ((((int)(particles[i+1].sX+boxShiftX) /// etc...
// prefetch boxes[nextBoxnum]
我刚刚注意到的最后一个 - 如果 box::rotDir 始终为 +/- 1.0,那么您可以像这样消除内部循环中的比较和分支:
const double rot = boxes[boxnum].rotDir; // always +/- 1.0
nX = particles[i].vX*Wxx + rot*particles[i].vY*Wxy;
nY = rot*particles[i].vX*Wyx + particles[i].vY*Wyy;
当然,应用之前和之后的分析通常需要注意。但我认为所有这些都可能会有所帮助,并且无论您是否切换到 SIMD 都可以完成。
【讨论】:
感谢您接受我的回答。这些有多大帮助?【参考方案2】:为了记录,还有 libSIMDx86!
http://simdx86.sourceforge.net/Modules.html
(编译时你也可以尝试:gcc -O3 -msse2 或类似的)。
【讨论】:
【参考方案3】:((int)(particles[i].sX+boxShiftX))/BOX_SIZE
如果 sX 是一个 int(无法判断),那就太昂贵了。在进入循环之前将 boxShiftX/Y 截断为 int。
【讨论】:
很遗憾,sX 和 boxShiftX 都是双精度数,其目的是为了有效地随机化舍入(boxShiftX 在 [-.5,.5] 范围内) 我不知道,当浮点数需要截断并取模时,我通常会使用 wtf。这是整数问题被感知准确性混淆的标志。一旦你去那里,通过缩放将浮点数转换为整数通常会得到很大的回报。像这样的代码的最终结果往往是整数,可能是屏幕上的一个像素。整数结果应该有整数数学。抱歉,我只是不知道您真正想做什么才能更有帮助。 我有这组粒子,并且正在将它们分类到“盒子”中。但是由于模拟的一个怪癖,盒子的位置必须在每个时间步跳来跳去,这就是发生这种情况的原因。【参考方案4】:您是否有足够的分析来告诉您在该函数中花费的时间在哪里?
例如,您确定在 boxnum 计算中花费时间的不是您的 div 和 mod?有时编译器无法发现可能的 shift/AND 替代方案,即使人类(或者至少知道 BOX_SIZE 和 BWIDTH/BHEIGHT,我不知道的人)可能能够做到。
很遗憾花大量时间 SIMDifying 错误的代码位...
可能值得研究的另一件事是,是否可以将工作强制转换为可以与 IPP 之类的库一起使用的东西,这将就如何最好地使用处理器做出明智的决定。
【讨论】:
老实说,它可能 是 div 和 mod,但不是;我还没有找到可以告诉我的分析器。对于我当前的实验,BOX_SIZE 是 1,你有一个很好的观点:BWIDTH,BHEIGHT 是 2 的幂。您对更细粒度的分析器有什么建议吗? 我希望任何采样分析器都能够为您提供每行信息,尽管编译器优化当然会使行匹配有点不精确。英特尔 vTune 将为您提供比单个汇编指令更细粒度的信息,因此如果您希望看到这些信息,那么这可能就是您要走的路。就个人而言,对于像这样简单(即小)的事情,我倾向于对代码进行多次运行计时,然后对其进行修改以查看花费时间的原因。【参考方案5】:您的算法有太多的内存、整数和分支指令,没有足够的独立触发器来从 SIMD 中获利。管道将不断停滞。
找到一种更有效的随机化方法将是首要任务。然后,尝试在 float 或 int 中工作,但不能同时在两者中工作。将条件重铸为算术,或至少作为选择操作。只有这样,SIMD 才能成为一个现实的命题
【讨论】:
以上是关于SIMD值得吗?有更好的选择吗?的主要内容,如果未能解决你的问题,请参考以下文章