Archived

This topic is now archived and is closed to further replies.

Greg K

The cost of virtual functions

Recommended Posts

Is there any slowdown when using virtual functions? I am thinking about heavily using virtual functions in my main loop but I don''t know if it is a good idea or not. If it is NOT a good idea, does anyone know of a suitable alternative. I did a basic test but the computer I am running right now (from university) may not be the most reliable. Thanks. -Greg Koreman

Share this post


Link to post
Share on other sites
There's a pointer reference in virtual functions, so it does take an additional operation in the CPU to pull off.

Compared to the rest of your algorithmic strategies, it shouldn't be a problem. Even game programming generally suffers the greatest during parses and submitting geometry to the renderer. Virtual functions don't even come close to the cost of those operations.

[edited by - Waverider on October 15, 2002 4:56:45 PM]

Share this post


Link to post
Share on other sites
Common virtual function call semantics :

struct foo_vtbl // virtual function table
{
foo_func0, // virtual functions
foo_func1,
foo_func2,
foo_func3,
...
};

class foo // class
{
foo_vtbl* pvtbl; // pointer to the vtbl, not user-accessible;

/* data */;
};

foo obj;
// obj.func1( param ); becomes
foo.pvtbl[1]( param );

In short, the cost is "one level of indirection" (one table lookup).

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]


[edited by - Fruny on October 15, 2002 4:58:21 PM]

Share this post


Link to post
Share on other sites
What if I were to do this:

class Object
{
Object(){ funcPointer = Update; }
funcPointer;

void Update( void ){}
}

class DerivedObject:Object
{
DerivedObject(){ funcPointer = Update; }
void Update( void ){}
}

instead of using virtual functions...?
-Greg Koreman

P.S. Update would obviously have stuff in it...

Share this post


Link to post
Share on other sites
No difference. In essence this is what the compiler does. In fact it''s possible that your implemention will be _slower_ then the equivalent compiler code; and of course, the greater cost: you''re duplicating code and making greater maintance costs.
It''s possible the compiler will be able to optimize away in certain cases virtual function calls. And in other cases, one would imagine such a simple operation of compilers have been optimized to the max

In short: don''t reinvent this wheel

Share this post


Link to post
Share on other sites
- At least as expensive ( the vtbl is a table of function pointers)
- Less readable / maintainable (for obvious reasons)

You might as well use the builtin language features instead of trying to reimplement the.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
At that point you''re beginning to simulate virtual tables. Additionally, every time an object is created, you would end up assigning "funcPointer" to each base class''s Update function.

Just suck the cost up; you''re more likely to cause serious hard to find bugs and waste your time looking for a way around viritual functions than if you would just use them.

A good analogy to your question is explained by Scott Meyers in "Effective C++". His analogy has to do with trying to avoid creating a temporary object by returning a reference. You have to read the item (Item 26) to fully enjoy his explaination as to why you should just suck it up.

In summary, write a solid, flexible design, THEN optimize

Share this post


Link to post
Share on other sites
quote:
Original post by Greg K
Is there any slowdown when using virtual functions?

Compared to what?
quote:

If it is NOT a good idea, does anyone know of a suitable alternative.

If you need runtime despatch, no. If you don''t need runtime despatch, don''t use virtual functions.

Share this post


Link to post
Share on other sites
quote:
Original post by SabreMan
Compared to what?



Compared to not using them.


[edited by - Waverider on October 16, 2002 8:11:15 AM]

Share this post


Link to post
Share on other sites
Eating food makes you fat.



The world holds two classes of men -- intelligent men without religion, and religious men without intelligence. - Abu''l-Ala-Al-Ma''arri (973-1057; Syrian poet)

Share this post


Link to post
Share on other sites
heheh, sage advice from Mr. Fines

The main-loop is probably the best place for virtual functions - they only get called once a frame then. Where you run into trouble, is when they need to be called many times per frame.

On modern hardware with modern compilers, there''s no significant performance difference between a function call and a virtual call. As Fruny mentioned it''s one table look-up, which (would be) nominally be 1 (pipelined) CPU tick (being read from the L1 cache) - however all function calls cause a pipe-line stall as they are branching instructions. The cost of the function call setup, return, and branch significantly outweigh the cost of the look-up iff the look-up does not cause any type of cache-miss. If it''s a soft L1 miss (found in L2 or L3 cache) the cost of the call setup and the look-up would be closer, and if it''s hard cache-miss (it has to actually read memory) then the look-up may exceed the call setup. Note that the function call itself can cause a code cache miss, and this is far more likely when using function pointers (e.g. virtual functions) then when calling ''normal'' functions.

If the functions do a minimal amount of work, performance can be greatly enhanced by inlining them. It''s not feasible to inline virtual functions (or a call made by function pointer), so using virtual methods for functions that perform little work is not advisable if they will be called often.

Consider this pathological example:

  
//Case 1

interface IPureOO
{
virtual ~IPureOO()=0{}
virtual int GetID()=0;
};
struct CImplOO : IPureOO
{
~CImplOO(){}
virtual int GetID(){return 1;}
};

//Case 2

struct NotSoPureOO
{
NotSoPureOO(int id) : ID(id) {}
const int ID;
int GetID(){return this->ID;}
};
struct ImplNotSoPure : NotSoPureOO
{
ImplNotSoPure() : NotSoPureOO(1) {}
}


The performance difference is astounding, though both implementations exhibit the same behavior. The difference is that second case takes advantage of the knowledge that every sub-class will implement GetID using an integer. Technically you lose flexibility in the second case, but it''s flexibility that will never be used.

Share this post


Link to post
Share on other sites