views:

93

answers:

2

I set up a (perhaps very unscientific) small test to determine the overhead of virtual functions in a one-level single inheritance and the results I got were, well, exactly the same when accessing the derived class polymorphically or when accessing it directly. What was a bit surprising was the order of magnitude of computation time that is introduced when any function is declared virtual (see results below).

Is there so much overhead when declaring member functions as such, and why is it still present even when accessing the derived class directly?

The code is as follows:

class base
{
public:
    virtual ~base() {}
    virtual uint func(uint i) = 0;
};

class derived : public base
{
public:
    ~derived() {}
    uint func(uint i) { return i * 2; }
};

uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived;  // or derived* myderived = ...

for(ushort i = 0; i < numIters; i++)
{
  clock_t start2, finish2;
  start2 = clock();

  for (uint j = 0; j < 100000000; ++j)
        k += mybase->func(j);

  finish2 = clock();
  l += (double) (finish2 - start2);
  std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;

}

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;

Results:

base* mybase = new derived; gives an average of ~338 ms.

derived* myderived = new derived; gives an average of ~338 ms.

Eliminating inheritance and removing virtual functions gives an average of ~38 ms.

That's almost 10 times less! So basically, if any function is declared virtual the overhead will always be identically present, even if I don't use it polymorphically?

Thanks.

+4  A: 

Accessing it "directly" is doing the same work as accessing it "indirectly".

When you call the function on myderived, the pointer stored there could point to some object of some class derived from derived. The compiler can't assume that it really is a derived object, it might be an object of a further derived class that overrides the virtual function, so there needs to be virtual function dispatch just like in the mybase case. In both cases the function is looked up in the virtual function table before it is called.

To call the function non-polymorphically, don't use a pointer:

derived myderived;
myderived.func(1); 

When you remove the virtual functions, the compiler can inline the function call so that you basically end up with a simple loop:

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;

This is much faster since you save the overhead of 100000000 function calls and the compiler might even be able to optimize the loop further in ways it wouldn't if there was a function call in it.

Note also that the difference between the inlined version and the virtual function call would be much less if the function did some real work. In this example the function body takes almost no time at all, so the costs for calling the function outweigh the costs for executing the body.

sth
I see thanks. What you said makes perfect sense. Agreed on your point on the function having too little work. The proportion is obviously skewed here, but it's good to know the function call overhead. So basically at the end of the day once you declare a function virtual and use it through a pointer you have paid almost if not all of the price of using virtual functions.
Kristian D'Amato
s/the compiler can't assume it's really a derived/the compiler doesn't assume it's really a derived/ . It is possible, since there are no classes more derived (note: does require whole-program optimisation)
MSalters
+1  A: 

Virtual functions cost essentially nothing. Most real performance issues are caused by needlessly bushy call trees doing things you would never guess to be a problem.

The way I find them is by pausing the app several times under the debugger, and examining the state, including the call stack. Here's an example of using that method to get a 43x speedup.

Mike Dunlavey
Thanks Mike that's a terrific method.
Kristian D'Amato