Jump to content
  • Advertisement
Sign in to follow this  
CTar

Virtual functions or function pointers

This topic is 4834 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I will soon create a little 3D math library, since it'll be used a lot I need to make it fast. I need to use optimizations like 3DNow!, SSE, SSE2, MMX(Might not include this) etc. The problem is that I dont know at compile-time what I'm gonna use. So I thought maybe having an interface, for a 3D vecotr: IVector3. Then I'ld provide classes like: class MMXVector3 : public IVector3; class SSEVector3 : public IVector3; class SSE2Vector3 : public IVector3; class 3DNowVector3 : public IVector3; The problem with this is that I think the virtual functions could have too big an impact on performance. Another approach I thought about was to create a Vector3 class and then have function pointers, something like this:
class Vector3
{
public:
    static void (Vector3::*Add)(Vector3&, Vector3&);
    static void (Vector3::*Subtract)(Vector3&,Vector3&);
}


There would of course be more functions. So have anyone tryed creating a math library before and found a good solution? EDIT: Changed functions in second example to static since that was what I thought about.

Share this post


Link to post
Share on other sites
Advertisement
Function pointers don't give you any speed advantage over virtual functions. In fact they may give you slightly worse performace if the data segment that are located in is allocated in a region with fewer memory accesses as virtual tables are generally located near each other and thus give you better cache and page locality.

What you may consider, if you set the option of which function family to call at initialization time, is self modifying code that copies the function body of the appropriate functions when your program starts up. Alternately, if this is information known at install time, you may be able to ship an abbreviate linker and the object code for the appropriate function bodies and link them at install time.

Share this post


Link to post
Share on other sites
For small operations on performance critical data types, like vectors, having runtime code path selection probably won't save you anything.
You're probably best supplying different version of your library, statically linked, and chose the binaries at install time (or at load, if you load your app through a small stub).

You could alternatively put larger functions, for which the vcall overhead would be neglible, in a library with dynamic selection of code (build function pointer table on first call for instance, where all pointers initially point to some function to check what version to run). These functions could include operations like matrix multiply, inverse, quaternion lerps and slerps, and functions that perform smaller operations on arrays of data.

Share this post


Link to post
Share on other sites
Use function pointer..but only if you are programming in C...:)

If you use C++ I dont see why you should not use virtual functions: they have at least the same efficiency but the code is cleaner, polymorphic...nice!

However the problem you posted is different.

As SiCrane wrote you can for example develop different dll and copy the right one at install time (never tried but it should not be impossible).

Only one consideration: are you really planning to optimize a simple Vector Math? You risk seriously to waste your time... ( ok for the expensive matrix4*matrix4 mult but a three float/double addition... )

It is simpler to insert the shortest functions in you header (the compiler may decide to inline them)(take also a look at the old but nice Unreal public source...)

Share this post


Link to post
Share on other sites
If you truely want to remove the virtual overhead, do something like template specialization. E.g.:

namespace processor {
class mmx {}; //tags...
class sse {};
class sse2 {};
class 3dnow {};
}

class vector3_common {
..implementation stuff common to all versions of vector3..
};

template < typename = void >
class vector3 : public vector3_common {
..generic implementation of vector3, if available..
};

template <>
class vector3< processor::mmx > : public vector3_common {
..implementation of vector3 using mmx instructions..
};

..etc for sse, sse2, 3dnow..

template < typename processor_type >
void do_a_bunch_of_math( void ) {
typedef vector3< processor_type > vector3_type; //typedefed for convenience

vector3_type a, b, c;
a = b + c;
...
}

template < typename processor_type >
void main_loop( void ) {
while ( true ) {
do_a_bunch_of_math< processor_type >();
}
}

int main () {
...
if ( processor_type == "mmx" ) main_loop< processor::mmx >();
else if ( processor_type == "sse" ) main_loop< processor::sse >();
}





This way, you make your choice once. There will be a good bit of code duplication, but the excess should remain paged out, hopefully.

Share this post


Link to post
Share on other sites
Unless you have a specific reason why you can't, just assume SSE support - all recent processors support SSE (P3, P4, Athlon XP, Athlon 64, Pentium M, Sempron, and recent Celerons). There's no point conditionally supporting SSE2 or SSE3 since any small speed gain you might see will probably be outweighed by the overhead of whatever mechanism you use for conditional selection. Also, if a processor supports SSE2/3 it will already be faster than anything that only supports SSE so if your code is already fast enough on your lowest end processor there's little benefit from being even faster on the high end. For games the important thing is how fast you are on your minimum spec - high end machines will always be faster and so will not be a problem. For other applications there may be more reason to optimize for the high end.

Share this post


Link to post
Share on other sites
You shouldn't use function pointers in C++. Use functionoids instead (I think this is what Stroustrup recommends). Functionoids are more powerful than function pointers as they offer a way to pass 'ctor' arguments to an array of functors.

I proposed some code in another thread involving dialog windows and capturing signals, but will repeat it here to give a flavour of the technique (you'll notice in the end that the code looks a lot like function pointers):



// Superclass of functionoid.
class MyDialog
{
public:
virtual ~MyDialog() = 0;

// State definition.
enum state
{
state_1_,
state_2_,
state_3_,
.
.
}

// Class-based signal values.
static const int on_;
static const int off_;
static const int error_;

};

inline MyDialog::~MyDialog(){}

const int MyDialog::on_ = 1;
const int MyDialog::off_ = 0;
const int MyDialog::error_ = -1;

// Interface to state classes.
class MyDialogIface : public MyDialog
{
public:
virtual ~MyDialogIface() = 0;
virtual void action(int,state &) = 0;
};

inline MyDialogIface::~MyDialogIface(){}

// State classes...
class state_1 : public MyDialogIface
{
public:
virtual void action(int signal, state & current)
{
switch(signal)
{
case on_:
// insert some action code here.
current = state_2_; // now reset the state.
break;

case off_:
// insert some action code here.
current = state_1_;
break;

case error_:
// insert some action code here.
current = state_3_;
break;

default:
// do nothing.
}
}
};

class state_2 : public MyDialogIface
{
// As above.
.
.
};

class state_3 : public MyDialogIface
{
// As above.
.
.
};

// Action class, builds array of actions.
class MyDialogAction : public MyDialog
{
public:
MyDialogAction();
void operator()(int);
vector<MyDialogIface*>pVec_;
state object_state_;
};

MyDialogAction::MyDialogAction()
:pVec_(3),
object_state_(state_1_)
{
// This MUST be in the same order as the superclass 'enum state {}' members. for some
// really funky behaviour, you could add ctor arguments.
pVec_[0] = new state_1();
pVec_[1] = new state_2();
pVec_[2] = new state_3();
}

// The following operator automatically invokes action, and resets state. All
// you need to do is to pass the signal!
void MyDialogAction::operator()(int signal)
{
pVec_[object_state_]->action(signal,object_state_);
}

// Now when you want to use this at any time, just put something like this in your code:
.
.
.
MyDialogAction action;
while(signal)
{
action(signal);
}
.
.
.



A good source of information on functionoids can be found at http://www.parashift.com/c++-faq-lite/pointers-to-members.html, which also illustrates how templates can be used to speed the execution of functionoids where needed. The beauty of this technique is that you can completely encapsulate complex behaviour and completely separate behaviour from logic.

--random

Share this post


Link to post
Share on other sites
For absolute speed, as far as I am aware, template metaprogramming is perhaps the best. Take a look at the Blitz++ project, which, I believe, uses template metaprogramming for math libraries. My understanding is that the library rivals Fortran in speed (in some cases is faster).

--random

Share this post


Link to post
Share on other sites
Yes, I wrote something like it in Java, used it and never had any problems with it. If you would like to try test program that uses such library it's on http://speedtestjavaold.irc4.net/speedte.jar

Of course it works with equation (A + va) so it somehow differs from what mathematicians calls vector.

I thing such high amount of different functions is unneeded. Majority of curent AMD processors have support for SSE2 instructions, and MMX is crap.
I think you'd need to do at most two versions, one that uses just eax like registers, the other on SSE2 (depends one data type). Also from my experience with 3D graphic, it might be nice to support standard Double data type. You know round circles... ^_^

GFX cards have however more nice support for low precision matrix equations. So we might expect these things would move away from CPU, or majority of such things.

Share this post


Link to post
Share on other sites
Thanks for all the answers it have helped me a lot, one thing which sounds good, but I didn't quite understand was:

Quote:
Original post by SiCrane
What you may consider, if you set the option of which function family to call at initialization time, is self modifying code that copies the function body of the appropriate functions when your program starts up.


I dont know how to copy the body of a function, could someone show me some code which does that or maybe point me to an article.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!