6

我有一些运行良好的代码,但我想让它运行得更好。我遇到的主要问题是它需要有一个嵌套的 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 次实验花了一周多的时间。

我想我的问题是,是否有任何方法可以处理这些长向量,而不仅仅是循环它们。我觉得应该有,但我找不到。

4

5 回答 5

6

我不确定 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也会对此有所帮助。


由于您按顺序访问粒子,因此您可以尝试__builtin_prefetch提前预取(例如在 GCC 上)一些粒子以减少缓存未命中。由于您以不可预知的顺序访问它们,因此在盒子上预取有点困难;你可以尝试类似的东西

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 都可以完成。

于 2010-07-18T21:49:29.507 回答
3

只是为了记录,还有 libSIMDx86!

http://simdx86.sourceforge.net/Modules.html

(在编译时你也可以尝试:gcc -O3 -msse2 或类似的)。

于 2010-07-19T08:33:21.963 回答
2
((int)(particles[i].sX+boxShiftX))/BOX_SIZE

如果 sX 是一个 int (无法判断),那会很昂贵。在进入循环之前将 boxShiftX/Y 截断为 int。

于 2010-07-18T19:40:22.783 回答
1

您是否有足够的分析来告诉您在该功能中花费的时间?

例如,您确定在 boxnum 计算中花费时间的不是您的 div 和 mods 吗?有时编译器无法发现可能的 shift/AND 替代方案,即使人类(或者至少,知道 BOX_SIZE 和 BWIDTH/BHEIGHT,我不知道的人)可能能够做到。

很遗憾花大量时间在 SIMDifying 错误的代码位上......

可能值得研究的另一件事是,是否可以将工作强制转换为可以与 IPP 之类的库一起使用的东西,这将对如何最好地使用处理器做出明智的决定。

于 2010-07-18T19:02:41.820 回答
1

你的算法有太多的内存、整数和分支指令,没有足够的独立触发器来从 SIMD 中获利。管道将不断停滞。

寻找一种更有效的随机化方法将是首要任务。然后,尝试在 float 或 int 中工作,但不能同时在两者中工作。将条件重铸为算术,或至少作为选择操作。只有这样,SIMD 才能成为一个现实的命题

于 2013-05-27T13:26:27.887 回答