Skip to main content
gamedev.net
🔒 Locked

FAAAAAASTER :-)

Started by Coma Mind Nov 22, 2000 at 8:52 AM 2 replies 200+ views
Original Post
Coma Mind
Coma Mind
Use the code below to save a (very small) amount of time: float sqrtf(float X) { __asm fld X __asm fsqrt } Explanation: Floating point values aren''t stored in x86 registers like integer values. Floating point vars like float or double are just left in the first floating point stack register (called st oder st(0)) so the calling function can easily use them. If you write float sqrts(float X) { __asm fld X __asm fsqrt __asm fstp X return X; } your C/C++ compiler will compile it to: fld dword ptr [ebp+04] fsqrt fstp dword ptr [ebp+04] fld dword ptr [ebp+04] ret As you can see, the return command for a floating point value is just a fld call to load it into the first floating point stack register. So if you just leave the sqrt in the register you get: fld dword ptr [ebp+04] fsqrt ret If you need more informations about registers email me (bjurke@gmx.de).
GOD of coders is John Carmack.....Marilyn Manson: KILL YOUR GOD!!!!
Jumpster
Jumpster
quote:

float sqrtf(float X)
{
__asm fld X
__asm fsqrt
}




There's a problem with this. At least if you define it as __inline. I have tried the exact same thing. The problem comes when the release version of the code is expecting a value other than the sqrt(x) to be in the st(0).

I haven't exactly pin-pointed the source of the error in *my* code but the compiler tries to optimize certain instructions. During the optimization process, certain assumptions are made.

With the assumption that the sqrt(x) stays into the st(0) - as this code shows to assume - many times my compiler's assumptions were incorrect.

My solution was to leave the code inlined, and change it to:


__inline float mysqrt( const float value )
{

float result = 0.0f;

__asm
{
fld dword ptr[value];
fsqrt;
fstp dword ptr[result];
}

return result;
}



I lost minimal time (literally) compared to what you have and the errors I received prior to this change were eliminated. Yes, the return command is literally replaced with

fld dword ptr [result] - but for some reason, optimization I'm sure, the instruction is not ALWAYS right after the others. It could be 6-8 instructions later... Pretty cool, eh?

However, another error reared it's ugly head... Division by Zero... Now, I wouldn't think that a division by zero exception would be thrown at such low level code, but it kept popping up for me. So, I went back to the drawing boards and came up with this as my final sqrt() function.

    
__inline float mysqrt( const float value )
{
float result = 0.0f;

__asm
{

fld dword ptr [value]; // Push the value onto the stack...

ftst; // Compare it 0.0f...

fnstsw ax; // Store the results in ax...


test eax, 16384; // Was (value == 0.0f)...?

jnz SQRT_END; // Yes. Return 0.0f...


fsqrt; // No. Calculate the sqrt now...

fstp dword ptr [result];// Let's save the result...


SQRT_END:

}

return result;
}


This function is roughly 320 Clock Cycles in debug mode (as opposed to 42,000 for the library's version) and roughly 60 Clock Cycles in release mode (opposed to 50 for the library's version).

Why is the library's version faster? Because the library's version does *not* check for divide by zero instances... It costs roughly an extra 10-cycles to do the little extra check.

Personally, I use this new function for both release and debug mode. The improvement over the debug mode is plenty worth it for me. When the time comes for a "Final Release" then maybe I'll revert back to the library's version of the function.

Regards,
Jumpster






Edited by - Jumpster on November 22, 2000 10:46:42 AM
Regards,JumpsterSemper Fi
kamrann
kamrann
Can I ask what you mean when you refer to ''the library''s version''? I''m new to programming and at the moment i''m using the sqrt function from the basic floating point operations. Is this what you mean, or for graphics should I be using some other math library?

Also, seeing as you obviously know a lot about programming, can I ask another question.

I''m currently doing most of my graphics engine myself, using D3D only really for the rasterizing. I have my own classes for CPoint3D, CVector3D and CMatrix, and i''m transforming and lighting myself - I know its slower than using D3D, but I want to learn how it works.
Anyway, I was profiling my simple program in VC++ 6, and found that large amounts of time ( 20% of my Transforming and lighting function ) seems to be taken up by default constructors and mostly copy constructors for my points and vectors. Even the totally empty default contructors use up time. Why is this and am I doing something wrong?

Also, if I write something like:

CVector3D vec1 = GetAVectorFromSomewhere(),

where the above function returns CVector3D, the copy constructor I think gets called twice, once for the function return, then again for initialization - this seems like a wasted copying; is it unavoidable?

thanks a lot

Cameron
Jumpster
Jumpster
quote:

Can I ask what you mean when you refer to ''the library''s version''? I''m new to programming and at the moment i''m using the sqrt function from the basic floating point operations. Is this what you mean...



Yes. That''s what I was referring to.

quote:

Anyway, I was profiling my simple program in VC++ 6, and found that large amounts of time ( 20% of my Transforming and lighting function ) seems to be taken up by default constructors and mostly copy constructors for my points and vectors. Even the totally empty default contructors use up time. Why is this and am I doing something wrong?

Also, if I write something like:

CVector3D vec1 = GetAVectorFromSomewhere(),

where the above function returns CVector3D, the copy constructor I think gets called twice, once for the function return, then again for initialization - this seems like a wasted copying; is it unavoidable?



I myself am still having problems with constructors. What I have done (and has helped a bit) was created several constructors:

  
class CVECTOR
{
float x;
float y;
float z;

// Default Constructor

CVECTOR() {x=y=z=0.0f;} // Changed from...

CVECTOR() : x(0.0f), y(0.0f), z(0.0f) {} // Changed to...


// Copy Constructor...

CVECTOR( const CVECTOR& src ) {x=src.x; y=src.y; z=src.z;} // From...

CVECTOR( const CVECTOR& src ) : x(src.x), y(src.y), z(src.z) {} // To...



// Another constructor...

CVECTOR( const float a, const float b, const float c ) { x=a; y=b; z=c; } // From...

CVECTOR( const float a, const float b, const float c ) : x(a), y(b), z(c) {} // To...


}



Basically, I began using the Initialization list where-ever I could. This has helped out a bit (noticeably but not dramatically) in my code. You may want to consider doing the same thing.

Two things to be warned about though - it really begins to eat into code readability so be carefull. Is the few extra clock-cycles you save worth the trouble of trying to identify what you are doing? Only you can answer that question...

The other is that initialization lists begin getting tricky when you work with sub-classes and such.


Regards,
Jumpster

P.S. Check out the article at: http://www.tantalon.com/pete/cppopt/main.htm too. This guy has some *really* good information on optimization techniques as well as do''s and don''ts of the practice.
Regards,JumpsterSemper Fi

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.