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

zeb*_*h49 6 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;
            }   
    }/*}}}*/
Run Code Online (Sandbox Code Playgroud)

我看过SIMD,虽然我找不到太多关于SIMD的信息,但我并不完全确定正确提取和打包数据所需的处理能够获得一半的指令,因为显然只是一次可以使用两个双打.

我尝试用shm和pthread_barrier分解成多个线程(以同步不同的阶段,其中上面的代码是一个),但它只是让它变慢.

我目前的代码确实很快; 它是每10M粒子*迭代一秒钟的量级,从我从gprof可以看出,我的30%的时间仅用于该函数(5000次调用; PART_COUNT = 8192粒子耗时1.8秒).我并不担心小而恒定的时间,只是512K粒子*50K迭代*1000次实验上次花了一个多星期.

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

cel*_*ion 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;
Run Code Online (Sandbox Code Playgroud)

这具有在最后不做额外添加的小的潜在副作用.


另一个潜在的加速是__restrict在粒子阵列上使用,这样编译器可以更好地优化对速度的写入.此外,如果Wxx等是全局变量,它们可能必须每次通过循环重新加载而不是可能存储在寄存器中; 使用__restrict也会有所帮助.


由于您按顺序访问粒子,因此您可以尝试预取(例如__builtin_prefetch在GCC上)前面的几个粒子以减少缓存未命中.由于您以不可预测的顺序访问它们,因此对盒子进行预取会有点困难; 你可以试试像

int nextBoxnum = ((((int)(particles[i+1].sX+boxShiftX) /// etc...
// prefetch boxes[nextBoxnum]
Run Code Online (Sandbox Code Playgroud)

我刚才注意到的最后一个 - 如果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;
Run Code Online (Sandbox Code Playgroud)

自然地,通常在应用之前和之后进行性能分析.但我认为所有这些都可能有所帮助,无论您是否切换到SIMD都可以完成.