void ShiftGrid(Vector shift)
{
_m128 p, v, res;
_mm_load_ps(v, &shift.x);
float *pdata = m_pGrid;
for(int i=0; i<gridsize; i++, pdata+=4)
{
p = _mm_load_ps(pdata);
res = _mm_add_ps(p, v);
_mm_store_ps(pdata, res);
}
}
🔒 Locked
SIMD techniques.
Advertisement
Original Post
I am using MS intrinsics to perform SIMD operations of sets of floats (4 element vectors ofcourse). All I am doing is adding a fixed vector to my array of vectors. They are stored as an array of structures. The code (from memory, I am not at my PC): This works fine, just want some tips on speeding it up. The data is generally about 16000 vectors. I know you can stream data from memory without going via the cache, and also force data to stream to the cache, but I don't know which is faster in this case, or quite how to implement either. Thanks!
void ShiftGrid(Vector shift){ _m128 p0,p1,p2,p3,v; _mm_load_ps(v, &shift.x); float *pdata = m_pGrid; for(int i=0; i<gridsize; i+=4, pdata+=16) { p0 = _mm_load_ps(pdata); p1 = _mm_load_ps(pdata+4); p2 = _mm_load_ps(pdata+8); p3 = _mm_load_ps(pdata+12); p0 = _mm_add_ps(p0,v); p1 = _mm_add_ps(p1,v); p2 = _mm_add_ps(p2,v); p3 = _mm_add_ps(p3,v); _mm_store_ps(pdata, p0); _mm_store_ps(pdata+4, p1); _mm_store_ps(pdata+8, p2); _mm_store_ps(pdata+12, p3); }}
You usually don't want to execute two *ps instructions on the same xmm* register in a row.
Okay thanks, thats good to know. How about cache usage and streaming, any advice there?
Also I've heard that using SIMD forces the FPU to switch modes (all the rest of my code is standard floating point stuff), which is fairly costly, so do I need to do anything to enforce FPU mode during the function, or will it not matter?
I would imagine FPU instructions in other threads would cause an FPU mode switch aswell, how do people get around that?
Also I've heard that using SIMD forces the FPU to switch modes (all the rest of my code is standard floating point stuff), which is fairly costly, so do I need to do anything to enforce FPU mode during the function, or will it not matter?
I would imagine FPU instructions in other threads would cause an FPU mode switch aswell, how do people get around that?
If you are using intrinsics, you don't have to worry about it. The compiler is taking care of cache related stuff on its own, and its usually pretty good.
The cache activity is pretty simple. Your only reading from one array. Between the compiler and the chips own automatic cache controls, its done.
The cache activity is pretty simple. Your only reading from one array. Between the compiler and the chips own automatic cache controls, its done.
Keep in mind that most SSE (or floating point instructions in general) have a latency of at least 3-4 cycles. (addps is 4 cycles on Athlon 64, and a good chunk more on P4) So try to ensure you have enough independent instructions to fill in those gaps. (After your addition, it takes 4+ cycles before the result is available to other instructions.
So one of the most important optimizations is to eliminate so many dependencies that this gap can be filled with other instructions (for example by unrolling the loop a few times)
So one of the most important optimizations is to eliminate so many dependencies that this gap can be filled with other instructions (for example by unrolling the loop a few times)
This is my function:
This is the disassembly of my function:
There are 7 xmm registers but my compiler is only making code that uses 2! (I'm using MSVC++ 2003).
Any way to make this better, or do I have to do the asm myself?
__m128 shift128, pt128_1, pt128_2, pt128_3, pt128_4; for(i=0; i<fours; i++, bpbuff+=16) { pt128_1 = _mm_load_ps(bpbuff); pt128_2 = _mm_load_ps(bpbuff+4); pt128_3 = _mm_load_ps(bpbuff+8); pt128_4 = _mm_load_ps(bpbuff+12); pt128_1 = _mm_add_ps(pt128_1, shift128); pt128_2 = _mm_add_ps(pt128_2, shift128); pt128_3 = _mm_add_ps(pt128_3, shift128); pt128_4 = _mm_add_ps(pt128_4, shift128); _mm_store_ps(bpbuff, pt128_1); _mm_store_ps(bpbuff+4, pt128_2); _mm_store_ps(bpbuff+8, pt128_3); _mm_store_ps(bpbuff+12, pt128_4); }This is the disassembly of my function:
for(i=0; i<fours; i++, bpbuff+=16)0049C310 mov dword ptr ,0 0049C317 jmp WW::LOD::PatchGrid::ShiftGrid+10Bh (49C32Bh) 0049C319 mov eax,dword ptr 0049C31C add eax,1 0049C31F mov dword ptr ,eax 0049C322 mov ecx,dword ptr [bpbuff] 0049C325 add ecx,40h 0049C328 mov dword ptr [bpbuff],ecx 0049C32B mov eax,dword ptr 0049C32E cmp eax,dword ptr [fours] 0049C331 jge WW::LOD::PatchGrid::ShiftGrid+25Ah (49C47Ah) { pt128_1 = _mm_load_ps(bpbuff);0049C337 mov eax,dword ptr [bpbuff] 0049C33A movaps xmm0,xmmword ptr [eax] 0049C33D movaps xmmword ptr [ebp-450h],xmm0 0049C344 movaps xmm0,xmmword ptr [ebp-450h] 0049C34B movaps xmmword ptr [pt128_1],xmm0 pt128_2 = _mm_load_ps(bpbuff+4);0049C352 mov eax,dword ptr [bpbuff] 0049C355 add eax,10h 0049C358 movaps xmm0,xmmword ptr [eax] 0049C35B movaps xmmword ptr [ebp-430h],xmm0 0049C362 movaps xmm0,xmmword ptr [ebp-430h] 0049C369 movaps xmmword ptr [pt128_2],xmm0 pt128_3 = _mm_load_ps(bpbuff+8);0049C370 mov eax,dword ptr [bpbuff] 0049C373 add eax,20h 0049C376 movaps xmm0,xmmword ptr [eax] 0049C379 movaps xmmword ptr [ebp-410h],xmm0 0049C380 movaps xmm0,xmmword ptr [ebp-410h] 0049C387 movaps xmmword ptr [pt128_3],xmm0 pt128_4 = _mm_load_ps(bpbuff+12);0049C38E mov eax,dword ptr [bpbuff] 0049C391 add eax,30h 0049C394 movaps xmm0,xmmword ptr [eax] 0049C397 movaps xmmword ptr [ebp-3F0h],xmm0 0049C39E movaps xmm0,xmmword ptr [ebp-3F0h] 0049C3A5 movaps xmmword ptr [pt128_4],xmm0 pt128_1 = _mm_add_ps(pt128_1, shift128);0049C3AC movaps xmm0,xmmword ptr [shift128] 0049C3B0 movaps xmm1,xmmword ptr [pt128_1] 0049C3B7 addps xmm1,xmm0 0049C3BA movaps xmmword ptr [ebp-3D0h],xmm1 0049C3C1 movaps xmm0,xmmword ptr [ebp-3D0h] 0049C3C8 movaps xmmword ptr [pt128_1],xmm0 pt128_2 = _mm_add_ps(pt128_2, shift128);0049C3CF movaps xmm0,xmmword ptr [shift128] 0049C3D3 movaps xmm1,xmmword ptr [pt128_2] 0049C3DA addps xmm1,xmm0 0049C3DD movaps xmmword ptr [ebp-3B0h],xmm1 0049C3E4 movaps xmm0,xmmword ptr [ebp-3B0h] 0049C3EB movaps xmmword ptr [pt128_2],xmm0 pt128_3 = _mm_add_ps(pt128_3, shift128);0049C3F2 movaps xmm0,xmmword ptr [shift128] 0049C3F6 movaps xmm1,xmmword ptr [pt128_3] 0049C3FD addps xmm1,xmm0 0049C400 movaps xmmword ptr [ebp-390h],xmm1 0049C407 movaps xmm0,xmmword ptr [ebp-390h] 0049C40E movaps xmmword ptr [pt128_3],xmm0 pt128_4 = _mm_add_ps(pt128_4, shift128);0049C415 movaps xmm0,xmmword ptr [shift128] 0049C419 movaps xmm1,xmmword ptr [pt128_4] 0049C420 addps xmm1,xmm0 0049C423 movaps xmmword ptr [ebp-370h],xmm1 0049C42A movaps xmm0,xmmword ptr [ebp-370h] 0049C431 movaps xmmword ptr [pt128_4],xmm0 _mm_store_ps(bpbuff, pt128_1);0049C438 movaps xmm0,xmmword ptr [pt128_1] 0049C43F mov eax,dword ptr [bpbuff] 0049C442 movaps xmmword ptr [eax],xmm0 _mm_store_ps(bpbuff+4, pt128_2);0049C445 movaps xmm0,xmmword ptr [pt128_2] 0049C44C mov eax,dword ptr [bpbuff] 0049C44F add eax,10h 0049C452 movaps xmmword ptr [eax],xmm0 _mm_store_ps(bpbuff+8, pt128_3);0049C455 movaps xmm0,xmmword ptr [pt128_3] 0049C45C mov eax,dword ptr [bpbuff] 0049C45F add eax,20h 0049C462 movaps xmmword ptr [eax],xmm0 _mm_store_ps(bpbuff+12, pt128_4);0049C465 movaps xmm0,xmmword ptr [pt128_4] 0049C46C mov eax,dword ptr [bpbuff] 0049C46F add eax,30h 0049C472 movaps xmmword ptr [eax],xmm0 }There are 7 xmm registers but my compiler is only making code that uses 2! (I'm using MSVC++ 2003).
Any way to make this better, or do I have to do the asm myself?
Not sure how to do this with compiler intrinsics, but you may want to use the load-execute form of addps. (Where the source operand is a memory address instead of an xmm register).
Saves you some instructions/latency as you don't have to do the two instructions separately (and wait for the load to complete before you can perform the add).
Must be because the compiler believes there's nothing to be gained by using the remaining 6. And it's usually right. In other words, if you manually wrote the asm, where the only change was to force it to use more registers, you'd probably see little to no improvement in performance. If there's a dependency that forces your program to wait for the previous instruction to complete anyway, then it might as well reuse the same register.
Try using array notation instead of a pointer for pdata.
Makes it easier for the compiler to figure out dependencies and aliasing. (It may detect some false dependencies because your stores and loads all use the same pointer.)
Finally, which compiler settings do you use? Are optimizations enabled?
Oh, and one final thing.
There's no point in reusing the same variable name as both operand and return value for your sse operations. (pt128_1 = _mm_add_ps(pt128_1, shift128), for example).
You might be better off storing the result into a different temp variable. The compiler can easily combine them back into the same physical register if it decides it's worth it. But it's trickier to go the opposite way (deciding that if a variable is used independently in two different places, they can be given different registers. Again, it might be creating name dependencies preventing the compiler from optimizing.
Saves you some instructions/latency as you don't have to do the two instructions separately (and wait for the load to complete before you can perform the add).
Quote:
There are 7 xmm registers but my compiler is only making code that uses 2! (I'm using MSVC++ 2003).
Must be because the compiler believes there's nothing to be gained by using the remaining 6. And it's usually right. In other words, if you manually wrote the asm, where the only change was to force it to use more registers, you'd probably see little to no improvement in performance. If there's a dependency that forces your program to wait for the previous instruction to complete anyway, then it might as well reuse the same register.
Try using array notation instead of a pointer for pdata.
Makes it easier for the compiler to figure out dependencies and aliasing. (It may detect some false dependencies because your stores and loads all use the same pointer.)
Finally, which compiler settings do you use? Are optimizations enabled?
Oh, and one final thing.
There's no point in reusing the same variable name as both operand and return value for your sse operations. (pt128_1 = _mm_add_ps(pt128_1, shift128), for example).
You might be better off storing the result into a different temp variable. The compiler can easily combine them back into the same physical register if it decides it's worth it. But it's trickier to go the opposite way (deciding that if a variable is used independently in two different places, they can be given different registers. Again, it might be creating name dependencies preventing the compiler from optimizing.
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement