views:

163

answers:

4

I have some code that runs fairly well, but I would like to make it run better. The major problem I have with it is that it needs to have a nested for loop. The outer one is for iterations (which must happen serially), and the inner one is for each point particle under consideration. I know there's not much I can do about the outer one, but I'm wondering if there is a way of optimizing something like:

    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;
            }   
    }/*}}}*/

I've looked at SIMD, though I can't find much about it, and I'm not entirely sure that the processing required to properly extract and pack the data would be worth the gain of doing half as many instructions, since apparently only two doubles can be used at a time.

I tried breaking it up into multiple threads with shm and pthread_barrier (to synchronize the different stages, of which the above code is one), but it just made it slower.

My current code does go pretty quickly; it's on the order of one second per 10M particle*iterations, and from what I can tell from gprof, 30% of my time is spent in that function alone (5000 calls; PART_COUNT=8192 particles took 1.8 seconds). I'm not worried about small, constant time things, it's just that 512K particles * 50K iterations * 1000 experiments took more than a week last time.

I guess my question is if there is any way of dealing with these long vectors that is more efficient than just looping through them. I feel like there should be, but I can't find it.

+1  A: 

Do you have sufficient profiling to tell you where the time is spent within that function?

For instance, are you sure it's not your divs and mods in the boxnum calculation where the time is being spent? Sometimes compilers fail to spot possible shift/AND alternatives, even where a human (or at least, one who knew BOX_SIZE and BWIDTH/BHEIGHT, which I don't) might be able to.

It would be a pity to spend lots of time on SIMDifying the wrong bit of the code...

The other thing which might be worth looking into is if the work can be coerced into something which could work with a library like IPP, which will make well-informed decisions about how best to use the processor.

Will Dean
Honestly, it probably *is* the divs and mods, but no; I have yet to find a profiler that will tell me that. For my current experiment, BOX_SIZE has been 1, and you have a good point: BWIDTH, BHEIGHT have been powers of two. Do you have a suggestion for a more fine-grained profiler?
zebediah49
I would expect any sampling profiler to be able to give you per-line info, though of course the compiler optimisation makes line matching a little imprecise. Intel vTune will give you information even more finely-grained than a single assembler instruction, so that might be the way to go if that's what you feel you want to see.Personally, for something simple (i.e. small) like this I tend to time the code over lots of runs and then hack about with it to see what's taking the time.
Will Dean
+2  A: 
((int)(particles[i].sX+boxShiftX))/BOX_SIZE

That's expensive if sX is an int (can't tell). Truncate boxShiftX/Y to an int before entering the loop.

Hans Passant
Unfortunately, both sX and boxShiftX are doubles, and the point of it is to effectively randomize rounding (boxShiftX is in the range [-.5,.5])
zebediah49
I dunno, I usually go wtf when floating point numbers need to be truncated and taken modulo. That's a sign of an integer problem being obfuscated with perceived accuracy. Once you go there, turning the floating point numbers into integers by scaling usually pays off big. The end result of code like this tends to be integer, maybe a pixel on the screen. Integer results should have integer math. Sorry, I just don't know what you are really trying to do to be more helpful.
Hans Passant
I have this set of particles, and am sorting them into 'boxes'. Due to a quirk of the simulation though, the location of the boxes has to jump around every timestep, which is why that happens.
zebediah49
+3  A: 

I'm not sure how much SIMD would benefit; the inner loop is pretty small and simple, so I'd guess (just by looking) that you're probably more memory-bound than anything else. With that in mind, I'd try rewriting the main part of the loop to not touch the particles array more than needed:

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;

This has the small potential side effect of not doing the extra addition at the end.


Another potential speedup would be to use __restrict on the particle array, so that the compiler can better optimize the writes to the velocities. Also, if Wxx etc. are global variables, they may have to get reloaded each time through the loop instead of possibly stored in registers; using __restrict would help with that too.


Since you're accessing the particles in order, you can try prefetching (e.g. __builtin_prefetch on GCC) a few particles ahead to reduce cache misses. Prefetching on the boxes is a bit tougher since you're accessing them in an unpredictable order; you could try something like

int nextBoxnum = ((((int)(particles[i+1].sX+boxShiftX) /// etc...
// prefetch boxes[nextBoxnum]

One last one that I just noticed - if box::rotDir is always +/- 1.0, then you can eliminate the comparison and branch in the inner loop like this:

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;

Naturally, the usual caveats of profiling before and after apply. But I think all of these might help, and can be done regardless of whether or not you switch to SIMD.

celion
Thanks for accepting my answer. How much did any of those help?
celion
+1  A: 

Just for the record, there's also libSIMDx86!

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

(On compiling you may also try: gcc -O3 -msse2 or similar).

cigit