SIMD值得吗?有更好的选择吗?

时间:2010-07-18 18:50:49

标签: c optimization simd

我有一些运行得相当好的代码,但我想让它运行得更好。我遇到的主要问题是它需要一个嵌套的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粒子*迭代一秒钟的量级,从我从gprof可以看出,我的30%的时间仅用于该函数(5000次调用; PART_COUNT = 8192粒子耗时1.8秒)。我并不担心小而恒定的时间,只是512K粒子* 50K迭代* 1000次实验上次花了一个多星期。

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

5 个答案:

答案 0 :(得分: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也会有所帮助。


由于您按顺序访问粒子,因此可以尝试预取(例如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都可以完成。

答案 1 :(得分:3)

仅供记录,还有libSIMDx86!

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

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

答案 2 :(得分:2)

((int)(particles[i].sX+boxShiftX))/BOX_SIZE

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

答案 3 :(得分:1)

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

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

花费大量时间在SIMD中错误地代码,真是太遗憾了......

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

答案 4 :(得分:1)

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

找到一种更有效的随机化方式将是最重要的。然后,尝试在float或int中工作,但不能同时工作。重构条件作为算术,或至少作为选择操作。只有这样,SIMD才能成为现实主张