Jump to content
  • Advertisement
Sign in to follow this  
Telastyn

Virtual Base Class Overhead?

This topic is 5479 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

When using virtual base classes:
class     foo: virtual public basefoo, virtual public basebar{
// stuff
}
what is the tradeoff being made? Or rather, can someone explain to me the pointer magic that's going on behind the scenes so that I can understand what sort of overhead [if any] comes with this feature?

Share this post


Link to post
Share on other sites
Advertisement
Guest Anonymous Poster
Virtual methods effectively do the following:

class base
{
public:
virtual void func1();
virtual void func2();
}


base* b = new b();

b->func1();

Breaks down to something like this:

(*b->_vtable[FUNC1_INDEX])();

where FUNC1_INDEX is assigned by the compiler (not suprisingly, at compile time!) and _vtable is a hidden pointer (if you do sizeof(base), you'll get the size you expect + 4.

So from an execution standpoint, you have an extra indirection due to the vtable lookup. If base was not virtual, the code would look like this:

base::func1(b);

But since it is virtual, you dereferce b to get to the vtable:

b->_vtable[FUNC1_INDEX](b);

In x86 assembler, that boils down to something like this (assumes "b" is in eax):

call [base::func] ; a static call

or for virtual functions:

mov ecx, [eax+FUNC1_INDEX*4]
call ecx


The extra move is the "cost" of virtual functions (which has cache implications, as you are performing pointer chasing which is generally considered bad).

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
That last thought wasn't so clear. When I say pointer chasing is usually considered bad, I don't mean to have such a strong emphesis. The value of virtual functions IMO greatly outweigh the percieved performance overhead.

I should know better than to make such a statement. Know your tools (the language and compiler) and apply your requirements to a situation, and let a profiler decide if it is your bottleneck.

Share this post


Link to post
Share on other sites
Ah, yes. Though the question was about virtual base classes, not functions, or does the same underlying behavior apply?

[note: though from my own little project and testing, they seem to break a fair bit of pointer flexibility...]

Share this post


Link to post
Share on other sites
to put it simply don't worry about it! If game runs slowly on a current machine then you've got way bigger problems that the overhead for useing a virtual baseclass. People often talk about the overhead as if it gives you a huge performance hit and should be avoided like the plauge but I've found that its usefullness far outweights the negligable overhead. I'd suggest useing whatever is easiest - then when your done if your code is slow find out where and fix it then, optimizing too early will just make it harder to code later. I've used a nice class heirachy for all my games and at work and never once has it been an issue!

Edit: There is no performance hit for defining a virtual base class ... but you want to call functions from it right so thats where the performance hit comes in - when you try to find the right function to actually execute (the parent or the childs version).

Share this post


Link to post
Share on other sites
I believe (without truely knowing), that the naive (simple) implementation of virtual base classes adds one level of indirection to accessing ANY of the base classes members ...

My reasoning is this:

If you do the following:

class A
{
int a;
void foo_a();
}
// objects are likely 4 bytes (total allocation)
// just the int "a"

class B : A
{
int c;
void foo_c();
}
// objects are likely 8 bytes total allocation
// first a, then b

class C : A
{
int c;
void foo_c();
}
// objects are likely 8 bytes total allocation
// first a, then c

class D : B, C
{
int d;
void foo_d();
}
// object is likely 20 bytes
// first a, then b, then a, then c, then d

// changing to virtual
class A
{
int a;
void foo_a();
}
// objects are likely 4 bytes (total allocation)
// just the int "a"

class B : virtual A
{
int c;
void foo_c();
}
// objects are likely 12 bytes total allocation
// an a somewhere, a pointer to the a (really a pointer to the "A" instance, then b

class C : virtual A
{
int c;
void foo_c();
}
// objects are likely 12 bytes total allocation
// an a somewhere, a pointer to the a (really a pointer to the "A" instance, then b

class D : B, C
{
int d;
void foo_d();
}
// object is likely 24 bytes
// an a somewhere (an implementation of A), a pointer to the a, then b, then a pointer to the a, then c, then d

// so you see, in this case 24 bytes might have been used in D ... but the real cost and benifit is the extra pointer to A's elements ... this slows down usage / access, but means that changes to A's elements through the B or C classes change the SAME value. Without virtual bases, Changes through B and D would use one value, changes through C would use the other copy.

Share this post


Link to post
Share on other sites
It looks like only ONE other responder even read the OP's question carefully ... he is asking about VIRTUAL BASE CLASSES, not virtual functions. They are very very similar in ideology and cost (both being about using a pointer instead of direct addressing, hence both using the name "virtual", but the reasons for them and common uses are quite different.

Virtual functions are for run time dispatch of function calls based on an object's type

Virtual base classes are for changing the same base class implementation (instance) between 2 or more derived classes.

So in my example you see, because B and C use virtual base classes, then derived classes which mix and match them will use a SHARED implementation of A, not seperate copies.

Share this post


Link to post
Share on other sites
Yes, yes, I understand the "use what works" approach. I'm not concerned about performance or even memory usage, but I'd like to know what they are. Making an uninformed design decision will lead to far greater problems than simply slow performance.

I wager that there will be a fairly insignificant memory increase which will likely be offset by only needing one instance of the base class[es]. I wager there's no performance overhead [a function table jump is a function table jump]. And I'd bet that it increases compile time a fair bit. But I don't know. And I worry that there are far greater implications than a few bytes here and there...

One thing I've found via trial is that using a base class pointer with a large virtually derived class pointer makes things blow up. The program doesn't when I store the "smaller" pointer though.

This just seems like a complete train-wreck waiting to happen.

Share this post


Link to post
Share on other sites
post the example you are refering to in the "blows up" and "doesn't" comments above ... I want to see what you are talking about exactly (and I'm going to bed, so won't respond until tommorow) ...

good luck.

Share this post


Link to post
Share on other sites
I actually found some documentation regarding the problem...

Here.

Basically using c-style downcasting on a class which is virtually derived from other classes.

Essentially in the code that led to [simplified] this:

BASE
base->TEXT
text->TEXTINPUT
base->GEN
base->LIST
gen+list+textinput->CONSOLE

[edit: sorry, the little diagram I made didn't display properly...]

The BASE class is a base render object class, which is an N-tree node that other scene graph objects inherit from. Its children though are stored as a type and a void pointer [a holdover from the previous non-inheriting, non-templated gui; which as you can imagine got a little unwieldy...].

The problem arose since each frame BASE walks through its children [which all inherit from BASE] and recursively calls BASE->render. Since the children are stored as void pointers [of the full class] BASE->render() would downcast the pointer into a BASE pointer. Using virtually derived classes broke this.

BASE->render() would call (BASE *)(CONSOLE ptr)->render(), which would point to... elsewhere. *boom*

Anyways, upcasting does seem to still work nicely. So now instead of storing (CONSOLE ptr) in the tree, I'm storing (BASE ptr) and casting it to CONSOLE when I need the added functionality. Since I store the type with the void pointer it's easy to check.

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.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!