🔒 Locked
SSE Performance ???
Advertisement
Original Post
hi all , i'm trying to do some inline assembler with regular math operation like length of vector , dot , cross and so on , I profile the functions with the QueryPerformanceCounter func and i must admitt that performance aren't here, is my compiler so good at writting assembler ? as an example i show you this results : theses funcs calculate the length of a vector, each is profiled with an array of 10 000 vectors float len(const CVector3& v1) { return sqrtf(v1.x*v1.x+v1.y*v1.y+v1.z*v1.z); } Result : took 699 Cycles Another implementation use an inline assembler for the square root : float SqrtSSE(float x) { __asm { movss xmm0, x rsqrtss xmm0, xmm0 rcpss xmm0, xmm0 movss x, xmm0 } return x; } float len(const CVector3& v1) { return SqrtSSE(v1.x*v1.x+v1.y*v1.y+v2.y*v2.y) } this func took : 600 cycles And finally the last one : the length function is in the class CVector3 so there are no intermediate function initialization in theory... float CVector3::len() { float f = x*x+y*y+z*z; __asm { movss xmm0, f rsqrtss xmm0, xmm0 rcpss xmm0, xmm0 movss f, xmm0 } return f; } this function took : 709 cycles So in conclusion : the worst is the member function with inlined assembler for the square root , the better is the call in a separate function for the square root in inlined assembler ???? and finally the C++ version 699 cycles so only a gain of 99 cycles ? what is your feeling about this ? i found it amazing.
Hi there :)
Actually, I experimented with the same thing a few days ago, but I didn't manage to get my SSE sqrt function much faster than the c library's in release mode. I guess it's pretty optimized as is.
But about the member function being slower, that makes no sense to me either... Perhaps it's something to do with member functions being slower overall.
I'd like to point out a performance issue with your SqrtSSE function however, your code actually first computes the reciprocal (inverse) square root with the rsqrtss instruction, and then inverses it again with rcpss, to get the square root... The relationship is like this: 1.0/InvSqrt(x) = Sqrt(x), 1.0/Sqrt(x) = InvSqrt(x). It's faster to use the sqrtss function instead, which calculates the square root in just a few cycles:
Also, by removing the rcpss instruction in your SqrtSSE function, you have a fast inverse square root function. :)
Actually, I experimented with the same thing a few days ago, but I didn't manage to get my SSE sqrt function much faster than the c library's in release mode. I guess it's pretty optimized as is.
But about the member function being slower, that makes no sense to me either... Perhaps it's something to do with member functions being slower overall.
I'd like to point out a performance issue with your SqrtSSE function however, your code actually first computes the reciprocal (inverse) square root with the rsqrtss instruction, and then inverses it again with rcpss, to get the square root... The relationship is like this: 1.0/InvSqrt(x) = Sqrt(x), 1.0/Sqrt(x) = InvSqrt(x). It's faster to use the sqrtss function instead, which calculates the square root in just a few cycles:
movss xmm0, xsqrtss xmm0, xmm0movss x, xmm0Also, by removing the rcpss instruction in your SqrtSSE function, you have a fast inverse square root function. :)
First you must understand what SSE should be used for. SSE is all about multiple data and correctly aligned multipel data at that.
In your case your processing a single vector at a time and even worse your doing a single float at a time. Then your using _asm. You should use intrinsic functions (Thats the '_mm' function in this case) instead. By using _asm you kill the compilers abillity to optimize because he doens't know what you do in your asm code and can't reuse any registers.
So to see what SSE can do. Align the vector array to 16 bytes then add an extra float to each vector so it's an even 16 bytes. After that you should process 4 vectors at a time by expanding your len function. By doing that you should get a speedup of about 3 or so.
Lastly why do you do a reciprocal square root (rsqrtss) then a reciprocal (rcpss)?
In your case your processing a single vector at a time and even worse your doing a single float at a time. Then your using _asm. You should use intrinsic functions (Thats the '_mm' function in this case) instead. By using _asm you kill the compilers abillity to optimize because he doens't know what you do in your asm code and can't reuse any registers.
So to see what SSE can do. Align the vector array to 16 bytes then add an extra float to each vector so it's an even 16 bytes. After that you should process 4 vectors at a time by expanding your len function. By doing that you should get a speedup of about 3 or so.
Lastly why do you do a reciprocal square root (rsqrtss) then a reciprocal (rcpss)?
You need to understand, wherever your putting in inline assembly, it messes up the optimization routines of the compiler, so unless your optimizing register passing, and stack tricks, both of which compilers/computers excel at, your not actually using anything close to the full power of assembly.
If you want to see some more performance gain, make the functions that use the assembly naked, and do all the stack work yourself. Pass all arguments as registers instead of pushing to the stack. Analyze your map of assembly functions, amd look for ways to keep common addresses in the same registers to limit excessive moves and data manipulations.
The compiler reallllly shines at all these micro optimizations, the assembly programmers really shines at optimizing complex algorithms that the compiler can't break down, that's where you should spend your energy if you program in assembly for speed gain.
If you want to see some more performance gain, make the functions that use the assembly naked, and do all the stack work yourself. Pass all arguments as registers instead of pushing to the stack. Analyze your map of assembly functions, amd look for ways to keep common addresses in the same registers to limit excessive moves and data manipulations.
The compiler reallllly shines at all these micro optimizations, the assembly programmers really shines at optimizing complex algorithms that the compiler can't break down, that's where you should spend your energy if you program in assembly for speed gain.
Good points there Xetick and ordered_disorder, it's pretty damned complicated, and unnecessary, to try to write inline assembly to optimize math code, but I do insist that even though you fail, it's a very good excercise. I learned alot of how compiler optimizations work, and how to write compiler-friendly code, by failing to make fast ASM routines. There's no harm in trying ;)
I'm kinda curious how ur testing it, as I imagine most of the time spent would be in retrieving the data into cache. Proper cache coherency, and data alignment, often net u large performance gains, particularily when it come to processing large arrays of data.
Also, I know in DJGPP, there is a way to make ur inline assembly 'play nice' with the compiler and roll ur code right in with its own, so u get all the fun little optimizations along with the inline assembly. I imagine ur using MSVC? Anyone know if the same can be done on that?
Also, I know in DJGPP, there is a way to make ur inline assembly 'play nice' with the compiler and roll ur code right in with its own, so u get all the fun little optimizations along with the inline assembly. I imagine ur using MSVC? Anyone know if the same can be done on that?
Quote:
Original post by Erim
Good points there Xetick and ordered_disorder, it's pretty damned complicated, and unnecessary, to try to write inline assembly to optimize math code, but I do insist that even though you fail, it's a very good excercise. I learned alot of how compiler optimizations work, and how to write compiler-friendly code, by failing to make fast ASM routines. There's no harm in trying ;)
Yah :} Assembly brings a huge smile to my face. There's so much magic and legend behind the assembly hackers of old(an era I missed due to not being born), that I often find myself coding sections of my c++ code in assembly, because I truely love coding in assembly, and it makes me feel like I am some hard core coder.
As you mentioned Erim, assembly makes you a better programmer. It won't make a huge difference, but it gives you an edge. Plus, it's kind of like reliving history :]
Argh, I am such a romantic when it comes to assembly....
From experience I found that there is no difference between sqrtss and fsqrt in execution performace... well, on the PIII architecture at least. Yes, I'm still using an archaic machine. :P
Perhaps it would be faster to calculate the square root via the approximated reciprocal square root instruction: x*rsqrtss(x). Even faster if you use the rsqrtps instuction to calculate the sqrt of 4 floats at once. For example:
Re. using __asm blocks vs. intrinsic functions, I found that they did not make too much difference for SSE and MMX, as those regs are rarely used by the optimiser anywhere else, even if I used the /arch:SSE compiler option. That said, they do become a problem if you use __asm blocks in small functions that are called often, especially those that are meant to be inlined, as optimisers may not be able to pass parameters via registers. However, __asm blocks are perfectly accepteable when you implement entire function blocks and inner loops in __asm ... say, for example when doing some sort of image filtering. Of course, you gotta know what you're doing, because sometimes you'll end up writing slower code if you're not careful (which basically comes down to ordered_disorder's closing comments). ;)
Perhaps it would be faster to calculate the square root via the approximated reciprocal square root instruction: x*rsqrtss(x). Even faster if you use the rsqrtps instuction to calculate the sqrt of 4 floats at once. For example:
float x[4];float r[4];__asm{rsqrtps xmm0,XMMWORD PTR [x] //Approx 1/sqrt(x) for all 4 floatsmulps xmm0,XMMWORD PTR [x] //Approx x/sqrt(x) for all 4 floatsmovaps XMMWORD PTR [r],xmm0 //Save the 4 results}Re. using __asm blocks vs. intrinsic functions, I found that they did not make too much difference for SSE and MMX, as those regs are rarely used by the optimiser anywhere else, even if I used the /arch:SSE compiler option. That said, they do become a problem if you use __asm blocks in small functions that are called often, especially those that are meant to be inlined, as optimisers may not be able to pass parameters via registers. However, __asm blocks are perfectly accepteable when you implement entire function blocks and inner loops in __asm ... say, for example when doing some sort of image filtering. Of course, you gotta know what you're doing, because sometimes you'll end up writing slower code if you're not careful (which basically comes down to ordered_disorder's closing comments). ;)
i think the best is to avoid this kind of optimization , i was investigating at it , in order to know if it could give me an extra power , but the complexity that it engage and the plateform dependance convince me to do not.
sniff... i was dreaming about a powerfull dot function or len function ...
if i understand , SSE is good for parallel scalar processing , so for matrix multiplication and multiple square root calculation , but the condition is to write full function naked ??
Can someone tell me some theory behind function call and initialization , i'm in lack in this domain...
sniff... i was dreaming about a powerfull dot function or len function ...
if i understand , SSE is good for parallel scalar processing , so for matrix multiplication and multiple square root calculation , but the condition is to write full function naked ??
Can someone tell me some theory behind function call and initialization , i'm in lack in this domain...
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement