Why is calling a virtual function faster than dynamic_cast?

I wrote a simple example that estimates the average call time of a virtual function using the base class interface and dynamic_cast and calling a non-virtual function. Here he is:

#include <iostream> #include <numeric> #include <list> #include <time.h> #define CALL_COUNTER (3000) __forceinline int someFunction() { return 5; } struct Base { virtual int virtualCall() = 0; virtual ~Base(){}; }; struct Derived : public Base { Derived(){}; virtual ~Derived(){}; virtual int virtualCall(){ return someFunction(); }; int notVirtualCall(){ return someFunction(); }; }; struct Derived2 : public Base { Derived2(){}; virtual ~Derived2(){}; virtual int virtualCall(){ return someFunction(); }; int notVirtualCall(){ return someFunction(); }; }; typedef std::list<double> Timings; Base* createObject(int i) { if(i % 2 > 0) return new Derived(); else return new Derived2(); } void callDynamiccast(Timings& stat) { for(unsigned i = 0; i < CALL_COUNTER; ++i) { Base* ptr = createObject(i); clock_t startTime = clock(); for(int j = 0; j < CALL_COUNTER; ++j) { Derived* x = (dynamic_cast<Derived*>(ptr)); if(x) x->notVirtualCall(); } clock_t endTime = clock(); double callTime = (double)(endTime - startTime) / CLOCKS_PER_SEC; stat.push_back(callTime); delete ptr; } } void callVirtual(Timings& stat) { for(unsigned i = 0; i < CALL_COUNTER; ++i) { Base* ptr = createObject(i); clock_t startTime = clock(); for(int j = 0; j < CALL_COUNTER; ++j) ptr->virtualCall(); clock_t endTime = clock(); double callTime = (double)(endTime - startTime) / CLOCKS_PER_SEC; stat.push_back(callTime); delete ptr; } } int main() { double averageTime = 0; Timings timings; timings.clear(); callDynamiccast(timings); averageTime = (double) std::accumulate<Timings::iterator, double>(timings.begin(), timings.end(), 0); averageTime /= timings.size(); std::cout << "time for callDynamiccast: " << averageTime << std::endl; timings.clear(); callVirtual(timings); averageTime = (double) std::accumulate<Timings::iterator, double>(timings.begin(), timings.end(), 0); averageTime /= timings.size(); std::cout << "time for callVirtual: " << averageTime << std::endl; return 0; } 

CallDynamiccast seems to be receiving almost twice as much.

time for callDynamiccast: 0.000240333

time for callVirtual: 0.0001401

Any ideas why?

EDITED: now the creation of the object is performed in the separete function, so compler does not know its real type. Almost the same result.

EDITED2: Create two different types of derived objects.

+7
source share
4 answers

A call to a virtual function is like a pointer to a function, or if the compiler knows the type, static dispatch. This is a constant time.

dynamic_cast is quite another - a specific implementation tool is used to determine the type. This is not a constant time, it can go through a class hierarchy (also consider multiple inheritance) and perform several searches. An implementation may use string comparisons. Therefore, complexity is higher in two dimensions. Real-time systems often avoid / obstruct dynamic_cast for these reasons.

More information is available in this document .

+11
source

It should be noted that the whole purpose of virtual functions is not to discard the inheritance graph. Virtual functions exist, so you can use an instance of a derived class as if it were a base class. So more specialized function implementations can be called from code that was originally called versions of the base class.

If virtual functions were slower than safely casting a function call to a derived class +, then C ++ compilers simply implement virtual function calls this way.

Therefore, there is no reason to expect that the call to dynamic_cast + will be faster.

+4
source

You simply measure the value of dynamic_cast<> . It is implemented using RTTI, which is optional in any C ++ compiler. Project + Properties, C / C ++, Language, Enable Run-Time Type Info. Change it to No.

You will now receive a careless reminder that dynamic_cast<> can no longer do the right job. Arbitrarily change it to static_cast<> to get very different results. The key point here is that if you know that upcast is always safe, then static_cast<> buys you the performance you're looking for. If you don't know that billowing is safe, then dynamic_cast<> saves you the trouble. This is a problem that is insanely difficult to diagnose. The usual failure mode is a bunch, you get an immediate GPF if you're really lucky.

+3
source

The difference is that you can call a virtual function on any instance obtained from Base . The notVirtualCall() element does not exist inside Base and cannot be called without first defining the exact dynamic type of the object.

The consequence of this difference is that the vtable of the base class includes a slot for virtualCall() , which contains a pointer to a function for the correct function to call. Thus, the virtual call simply chases the vtable pointer, which is included as the first (invisible) member of all Base objects, loads the pointer from the slot corresponding to virtualCall() , and calls the function behind this pointer.

If you execute dynamic_cast<> , on the contrary, the Base class does not know at compile time which of the other classes will eventually receive it. Therefore, it cannot include information in its vtable, which facilitates the resolution of dynamic_cast<> . This is a lack of information that makes dynamic_cast<> more expensive than calling a virtual function. dynamic_cast<> should actually search in the inheritance tree of the actual object to check if the target cast type is found among its bases. This is a job that virtual calling avoids.

0
source

All Articles