views:

1108

answers:

12

Having at least one virtual method in a C++ class (or any of its parent classes) means that the class will have a virtual table, and every instance will have a virtual pointer.

So the memory cost is quite clear. The most important is the memory cost on the instances (especially if the instances are small, for example if they are just meant to contain an integer: in this case having a virtual pointer in every instance might double the size of the instances. As for the memory space used up by the virtual tables, I guess it is usually negligible compared to the space used up by the actual method code.

This brings me to my question: is there a measurable performance cost (i.e. speed impact) for making a method virtual? There will be a lookup in the virtual table at runtime, upon every method call, so if there are very frequent calls to this method, and if this method is very short, then there might be a measurable performance hit? I guess it depends on the platform, but has anyone run some benchmarks?

The reason I am asking is that I came across a bug that happened to be due to a programmer forgetting to define a method virtual. This is not the first time I see this kind of mistake. And I thought: why do we add the virtual keyword when needed instead of removing the virtual keyword when we are absolutely sure that it is not needed? If the performance cost is low, I think I will simply recommend the following in my team: simply make every method virtual by default, including the destructor, in every class, and only remove it when you need to. Does that sound crazy to you?

Thanks for you kind advice.

+4  A: 

If you need the functionality of virtual dispatch, you have to pay the price. The advantage of C++ is that you can use a very efficient implementation of virtual dispatch provided by the compiler, rather than a possibly inefficient version you implement yourself.

However, lumbering yourself with the overhead if you don't needx it is possibly going a bit too far. And most classesare not designed to be inherited from - to create a good base class requires more than making its functions virtual.

anon
A: 

Depending on your platform, the overhead of a virtual call can be very undesirable. By declaring every function virtual you're essentially calling them all through a function pointer. At the very least this is an extra dereference, but on some PPC platforms it will use microcoded or otherwise slow instructions to accomplish this.

I'd recommend against your suggestion for this reason, but if it helps you prevent bugs then it may be worth the trade off. I can't help but think that there must be some middle ground that is worth finding, though.

Dan Olson
+6  A: 

See also the Stack Overflow question: AI Applications in C++: How costly are virtual functions? What are the possible optimizations?

Colin
+16  A: 

I ran some timings on a 3ghz in-order PowerPC processor. On that architecture, a virtual function call costs 7 nanoseconds longer than a direct (non-virtual) function call.

So, not really worth worrying about the cost unless the function is something like a trivial Get()/Set() accessor, in which anything other than inline is kind of wasteful. A 7ns overhead on a function that inlines to 0.5ns is severe; a 7ns overhead on a function that takes 500ms to execute is meaningless.

The big cost of virtual functions isn't really the lookup of a function pointer in the vtable (that's usually just a single cycle), but that the indirect jump usually cannot be branch-predicted. This can cause a large pipeline bubble as the processor cannot fetch any instructions until the indirect jump (the call through the function pointer) has retired and a new instruction pointer computed. So, the cost of a virtual function call is much bigger than it might seem from looking at the assembly... but still only 7 nanoseconds.

Edit: Andrew, Not Sure, and others also raise the very good point that a virtual function call may cause an instruction cache miss: if you jump to a code address that is not in cache then the whole program comes to a dead halt while the instructions are fetched from main memory. This is always a significant stall: on Xenon, about 650 cycles (by my tests).

However this isn't a problem specific to virtual functions because even a direct function call will cause a miss if you jump to instructions that aren't in cache. What matters is whether the function has been run before recently (making it more likely to be in cache), and whether your architecture can predict static (not virtual) branches and fetch those instructions into cache ahead of time. My PPC does not, but maybe Intel's most recent hardware does.

My timings control for the influence of icache misses on execution (deliberately, since I was trying to examine the CPU pipeline in isolation), so they discount that cost.

Crashworks
+1 I'm glad you ran those timings, it confirms my suspicions: virtual methods don't cost much. Thanks a lot.
MiniQuark
The cost in cycles is roughly equal to the number of pipeline stages between fetch and the end of the branch-retire. It's not an insignificant cost, and it can add up, but unless you're trying to write a tight high-performance loop there are probably bigger perf fish for you to fry.
Crashworks
7 nano seconds longer than what. If a normal call is 1 nano second that is dignificant if a normal call is 70 nano seconds then it is not.
Martin York
If you look at the timings, I found that for a function that cost 0.66ns inline, the differential overhead of a direct function call was 4.8ns and a virtual function 12.3ns (compared to the inline). You make the good point that if the function itself cost a millisecond, then 7 ns means nothing.
Crashworks
7ns? Heck, a photon can travel all the way across my office in that amount of time. It just shows how spoiled we are :-)
Mike Dunlavey
Your timings and test overlook the cost of cache misses that virtual functions will incur in large programs, and that is very misleading. If memory serves, a Xenon processor stalls for about 900 cycles on a cache miss.
Not Sure
More like 600 cycles, but it's a good point. I left it out of the timings because I was interested in just the overhead due to the pipeline bubble and prolog/epilog. The icache miss happens just as easily for a direct function call (Xenon has no icache branch predictor).
Crashworks
+9  A: 

There is definitely measurable overhead when calling a virtual function - the call must use the vtable to resolve the address of the function for that type of object. The extra instructions are the least of your worries. Not only do vtables prevent many potential compiler optimizations (since the type is polymorphic the compiler) they can also thrash your I-Cache.

Of course whether these penalties are significant or not depends on your application, how often those code paths are executed, and your inheritance patterns.

In my opinion though, having everything as virtual by default is a blanket solution to a problem you could solve in other ways.

Perhaps you could look at how classes are designed/documented/written. Generally the header for a class should make quite clear which functions can be overridden by derived classes and how they are called. Having programmers write this documentation is helpful in ensuring they are marked correctly as virtual.

I would also say that declaring every function as virtual could lead to more bugs than just forgetting to mark something as virtual. If all functions are virtual everything can be replaced by base classes - public, protected, private - everything becomes fair game. By accident or intention subclasses could then change the behavior of functions that then cause problems when used in the base implementation.

Andrew Grant
The biggest lost optimization is inlining, especially if the virtual function is often small or empty.
Zan Lynx
@Andrew: interesting point of view. I somewhat disagree with your last paragraph, though: if a base class has a function `save` that relies on a specific implementation of a function `write` in the base class, then it seems to me that either `save` is poorly coded, or `write` should be private.
MiniQuark
I second MiniQuark, but I voted up anyways :)
rmeador
Just because write is private doesn't prevent it from being overridden. This is another argument for not making things virtual by default. In any case I was thinking of the opposite - a generic and well written implementation is replaced by something that has specific and non-compatible behavior.
Andrew Grant
Voted up on the caching - on any large object-oriented code base, if you're not following code-locality performance practices, it is very easy for your virtual calls to cause cache misses and cause a stall.
Not Sure
And an icache stall can be really serious: 600 cycles in my tests.
Crashworks
@Zan - a virtual function can still be inlined, if the object type is known at compile time. I.e. object.method() instead of pobject->method(), and object is not a reference. But I agree that probably doesn't happen often.
Mark Ransom
The comments in this thread are just as interesting as the answers themselves: thanks everyone! :-)
MiniQuark
All methods are virtual in Java, that's where I got the idea. But they do have two protections against bugs that could come up if a derived class redefines a method that the base class relies on: 1) the `final` keyword for methods, and 2) private methods are automatically final.
MiniQuark
A: 

Just to give an idea of the cost, I just read something about it in Code Complete by Steve McConnell, chapter 25.3:

the relative costs:

  • integer assignment: 1
  • Call routine with no parameters: 1
  • Call private routine with 1 parameter: 1.5
  • Derived routine call: 2
  • Polymorphic routine call: 2.5

Though tis is based on old data from the first edition of the book, and can vary greatly depending on compiler, system, ...

The only way to know is to measure

His conclusions:

"Most of the common operations are about the same price—routine calls, assignments, integer arithmetic, and floating-point arithmetic are all roughly equal. Transcendental math functions are extremely expensive. Polymorphic routine calls are a bit more expensive than other kinds of routine calls."

Emile Vrijdags
The relative costs of routine calls simply compare the *startup* time, which is usually very small compared to the actual execution of the routine itself (so these measurements only apply to empty routines).
MiniQuark
the information about the costs is highly outdated. These maybe holds true only for Pentium 1 or similar processors. At all newer processors the bottleneck is instruction prefetcher which not only loads data slowly, it has to know which address to load from. Therefore processor stalls until address is resolved. This costs more than twenty times more than any simple integer operation.
buratinas
A: 

It will require just a couple of extra asm instruction to call virtual method.

But I don't think you worry that fun(int a, int b) has a couple of extra 'push' instructions compared to fun(). So don't worry about virtuals too, until you are in special situation and see that it really leads to problems.

P.S. If you have a virtual method, make sure you have a virtual destructor. This way you'll avoid possible problems


In response to 'xtofl' and 'Tom' comments. I did small tests with 3 functions:

  1. Virtual
  2. Normal
  3. Normal with 3 int parameters

My test was a simple iteration:

for(int it = 0; it < 100000000; it ++) {
    test.Method();
}

And here the results:

  1. 3,913 sec
  2. 3,873 sec
  3. 3,970 sec

It was compiled by VC++ in debug mode. I did only 5 tests per method and computed the mean value (so results may be pretty inaccurate)... Any way, the values are almost equal assuming 100 million calls. And the method with 3 extra push/pop was slower.

The main point is that if you don't like the analogy with the push/pop, think of extra if/else in your code? Do you think about CPU pipeline when you add extra if/else ;-) Also, you never know on what CPU the code will be running... Usual compiler can generates code more optimal for one CPU and less optimal for an other (Intel C++ Compiler)

alex2k8
the extra asm might just trigger a page fault (that wouldn't be there for non-virtual functions) - I think you hugely oversimplify the problem.
xtofl
+1 to xtofl's comment. Virtual functions introduce indirection, which introduce pipeline "bubbles" and affect caching behavior.
Tom
A: 

Here is link from C++ FAQ http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4

may be this will help!!

Alien01
+1  A: 

The extra cost is virtually nothing in most scenarios. (pardon the pun). ejac has already posted sensible relative measures.

The biggest thing you give up is possible optimizations due to inlining. They can be especially good if the function is called with constant parameters. This rarely makes a real difference, but in a few cases, this can be huge.


Regarding optimizations:
It is important to know and consider the relative cost of constructs of your language. Big O notation is onl half of the story - how does your application scale. The other half is the constant factor in front of it.

As a rule of thumb, I wouldn't go out of my way to avoid virtual functions, unless there are clear and specific indications that it is a bottle neck. A clean design always comes first - but it is only one stakeholder that should not unduly hurt others.


Contrived Example: An empty virtual destructor on an array of one million small elements may plow through at least 4MB of data, thrashing your cache. If that destructor can be inlined away, the data won't be touched.

When writing library code, such considerations are far from premature. You never know how many loops will be put around your function.

peterchen
+3  A: 

It depends. :) (Had you expected anything else?)

Once a class gets a virtual function, it can no longer be a POD datatype, (it may not have been one before either, in which case this won't make a difference) and that makes a whole range of optimizations impossible.

std::copy() on plain POD types can resort to a simple memcpy routine, but non-POD types have to be handled more carefully.

Construction becomes a lot slower because the vtable has to be initialized. In the worst case, the difference in performance between POD and non-POD datatypes can be significant.

In the worst case, you may see 5x slower execution (that number is taken from a university project I did recently to reimplement a few standard library classes. Our container took roughly 5x as long to construct as soon as the data type it stored got a vtable)

Of course, in most cases, you're unlikely to see any measurable performance difference, this is simply to point out that in some border cases, it can be costly.

However, performance shouldn't be your primary consideration here. Making everything virtual is not a perfect solution for other reasons.

Allowing everything to be overridden in derived classes makes it much harder to maintain class invariants. How does a class guarantee that it stays in a consistent state when any one of its methods could be redefined at any time?

Making everything virtual may eliminate a few potential bugs, but it also introduces new ones.

jalf
A: 

While everyone else is correct about the performance of virtual methods and such, I think the real problem is whether the team knows about the definition of the virtual keyword in C++.

Consider this code, what is the output?

#include <stdio.h>

class A
{
public:
    void Foo()
    {
     printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
     printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

Nothing surprising here:

A::Foo()
B::Foo()
A::Foo()

As nothing is virtual. If the virtual keyword is added to the front of Foo in both A and B classes, we get this for the output:

A::Foo()
B::Foo()
B::Foo()

Pretty much what everyone expects.

Now, you mentioned that there are bugs because someone forgot to add a virtual keyword. So consider this code (where the virtual keyword is added to A, but not B class). What is the output then?

#include <stdio.h>

class A
{
public:
    virtual void Foo()
    {
     printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
     printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

Answer: The same as if the virtual keyword is added to B? The reason is that the signature for B::Foo matches exactly as A::Foo() and because A's Foo is virtual, so is B's.

Now consider the case where B's Foo is virtual and A's is not. What is the output then? In this case, the output is

A::Foo()
B::Foo()
A::Foo()

The virtual keyword works downwards in the hierarchy, not upwards. It never makes the base class methods virtual. The first time a virtual method is encountered in the hierarchy is when the polymorphism begins. There isn't a way for later classes to make previous classes have virtual methods.

Don't forget that virtual methods mean that this class is giving future classes the ability to override/change some of its behaviors.

So if you have a rule to remove the virtual keyword, it may not have the intended effect.

The virtual keyword in C++ is a powerful concept. You should make sure each member of the team really knows this concept so that it can be used as designed.

Tommy Hui
Hi Tommy, thanks for the tutorial. The bug we had was due to a missing "virtual" keyword in a method of the base class. BTW, I am saying make *all* functions virtual (not the opposite), then, when clearly it is not needed, remove the "virtual" keyword.
MiniQuark