1

What is the performance difference between calling a virtual function from a derived class pointer directly vs from a base class pointer to the same derived class?

In the derived pointer case, will the call be statically bound, or dynamically bound? I think it'll be dynamically bound because there's no guarantee the derived pointer isn't actually pointing to a further derived class. Would the situation change if I have the derived class directly by value (not through pointer or reference)? So the 3 cases:

  1. base pointer to derived
  2. derived pointer to derived
  3. derived by value

I'm concerned about performance because the code will be run on a microcontroller.

Demonstrating code

struct Base {
    // virtual destructor left out for brevity
    virtual void method() = 0;
};

struct Derived : public Base {
    // implementation here
    void method() {
    }
}

// ... in source file
// call virtual method from base class pointer, guaranteed vtable lookup
Base* base = new Derived;
base->method();

// call virtual method from derived class pointer, any difference?
Derived* derived = new Derived;
derived->method();

// call virtual method from derived class value
Derived derivedValue;
derived.method();
Community
  • 1
  • 1
LemonPi
  • 1,026
  • 9
  • 22
  • 1
    Do you have a choice? Why use pointers and virtual functions, if you can use a value? If you *need* a polymorphic call, then you have to use one. – Bo Persson Apr 06 '17 at 10:48
  • If you have a question about performance you need to measure it. – Richard Critten Apr 06 '17 at 10:50
  • 3
    "Guaranteed vtable lookup" - no, any time the type is statically known it could be devirtualized, and in fact this case is devirtualized by both Clang and GCC. – harold Apr 06 '17 at 10:51
  • @harold, when is type statically known? – Basilevs Apr 06 '17 at 10:54
  • @BoPersson I can get the derived class by value through templates, but I don't think that's relevant to the question. – LemonPi Apr 06 '17 at 10:58
  • 2
    @Basilevs that depends on much optimization the compiler does, but in the above example it doesn't have to do much effort to find that the runtime type of the object is whatever it just constructed. – harold Apr 06 '17 at 10:59
  • 1
    @harold The example above is clearly a clarifying simplification... I also don't want to base my judgement on compiler-specific optimizations. The code will run on a microcontroller and the compiler is not as fancy as the latest GCC or clang. – LemonPi Apr 06 '17 at 11:03
  • 1
    @LemonPi then what can we say about it? We don't know the actual code or the actual compiler, anything could happen. Just check the asm then – harold Apr 06 '17 at 11:06
  • I guess, the question is - which optimisations are guaranteed by standard? Probably none. – Basilevs Apr 10 '17 at 04:43
  • @Basilevs Programming language standards that I know of are specified in term of "observable behavior". The CPU time is not an "observable" and couldn't be specified in a CPU independent way even if programming language standard writers wanted to - and they don't. – curiousguy Apr 11 '17 at 01:14

4 Answers4

4
  • In theory, the only C++ syntax that makes a difference is a member function call that uses qualified member name. In terms of your class definitions that would be

    derived->Derived::method();
    

    This call ignores the dynamic type of the object and goes directly to Derived::method(), i.e. it's bound statically. This is only possible for calling methods declared in the class itself or in one of its ancestor classes.

    Everything else is a regular virtual function call, which is resolved in accordance with the dynamic type of the object used in the call, i.e. it is bound dynamically.

  • In practice, compilers will strive to optimize the code and replace dynamically-bound calls with statically-bound calls in contexts where the dynamic type of the object is known at compile time. For example

    Derived derivedValue;
    derivedValue.method();
    

    will typically produce a statically-bound call in virtually every modern compiler, even though the language specification does not provide any special treatment for this situation.

    Also, virtual method calls made directly from constructors and destructors are typically compiled into statically-bound calls.

    Of course, a smart compiler might be able to bind the call statically in a much greater variety of contexts. For example, both

    Base* base = new Derived;
    base->method();
    

    and

    Derived* derived = new Derived;
    derived->method();
    

    can be seen by the compiler as trivial situations that easily allow for statically-bound calls.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
1

Virtual functions must be compiled to work as if they were always called virtually. If your compiler compiles a virtual call as a static call, that's an optimization that must satisfy this as-if rule.

From this, it follows that the compiler must be able to prove the exact type of the object in question. And there are some valid ways in which it can do this:

  • If the compiler sees the creation of the object (the new expression or the automatic variable from which the address is taken) and can prove that that creation is actually the source of the current pointer value, that gives it the precise dynamic type it needs. All your examples fall into this category.

  • While a constructor runs, the type of the object is exactly the class containing the running constructor. So any virtual function call made in a constructor can be resolved statically.

  • Likewise, while a destructor runs, the type of the object is exactly the class containing the running destructor. Again, any virtual function call can be resolved statically.

Afaik, these are all the cases that allow the compiler to convert a dynamic dispatch into a static call.

All of these are optimizations, though, the compiler may decide to perform the runtime vtable lookup anyway. But good optimizing compilers should be able to detect all three cases.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
-1

There should be no difference between the first two cases, since the very idea of virtual functions is to call always the actual implementation. Leaving compiler optimisations aside (which in theory could optimise all virtual function calls away if you construct the object in the same compilation unit and there is no way the pointer can be altered in between), the second call must be implemented as a indirect (virtual) call as well, since there could be a third class inheriting from Derived and implementing that function as well. I would assume that the third call will not be virtual, since the compiler knows the actual type already at compile time. Actually you could make sure of this by not defining the function as virtual, if you know you will always do the call on the derived class directly.

For really lightweight code running on a small microcontroller I would recommend avoiding defining functions as virtual at all. Usually there is no runtime abstraction required. If you write a library and need some kind of abstraction, you can maybe work with templates instead (which give you some compile-time abstraction).

At least on PC CPUs I often find virtual calls one of the most expensive indirections you can have (probably because branch prediction is more difficult). Sometimes one can also transform the indirection to the data level, e.g. you keep one generic function which operates on different data which is indirected with pointers to the actual implementation. Of course this will work only in some very specific cases.

-3

At run-time.

BUT: Performance as compared to what? It isn't valid to compare a virtual function call to a non-virtual function call. You need to compare it to a non-virtual function call plus an if, a switch, an indirection, or some other means of providing the same function. If the function doesn't embody a choice among implementations, i.e. doesn't need to be virtual, don't make it virtual.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • The question asks about distinction between different call contexts, and asks how to distinguish virtual call from a static one. Basically, this answers the title, not the question. Updated title to better match the question body. – Basilevs Apr 06 '17 at 10:51
  • @Basilevs 'At run time' = 'dynamically bound', but the real question is about performance *differences,* and it is invalid as shown in the answer. You don't appear to have read beyond the first three words. – user207421 Apr 06 '17 at 21:58