Virtual method VS std::function member variable in terms of performance
Asked Answered
H

1

13

A game engine has this class :

class  MouseListener{
public :
MouseListener();
virtual void OnMouseDown(int mx,int my);
virtual void OnMouseUp(int mx,int my);
.
.
.
};

Each object that wants to listen to mouse input , have to inherent that class and override it's methods . To not have to declare a new type each time , the class is modified to :

class  MouseListener{
public :
MouseListener();
std::function <void(MouseListener*,int,int)>OnMouseDown;
std::function <void(MouseListener*,int,int)>OnMouseUp;
.
.
.
};

now using the class can be done this way :

MouseListener * m = new MouseListener();
m->OnMouseDown = [](MouseListener * thiz,int x,int y){
   //// do something
};

From the input system , only the functions of MouseListener that are not null ( assigned ) are called ( with thiz = the mouse listener ). knowing that the class is used from an external library ( static link ) , which is better in terms of performance ?

NOTE : None of those function will be called unless a mouse event is received , when that happens , the appropriate function is called for each object listening to mouse input ( not supposed to be lot , <50)

Herminiahermione answered 18/12, 2016 at 13:19 Comment(9)
"I got rid of the overhead of calling a virtual function" false; you actually added at least one indirection step. std::function internally is implemented with virtual functions as well. Instead of retrieving your class vtable slot and doing an indirect call, the compiler has to extract the vtable from the pointer hidden into the std::function and do an indirect call over it. As an extra disadvantage, std::function is a beast complicated enough that I've never seen the compiler do any devirtualization over it, when in many cases it is done on "regular" virtual functions.Brachial
"I got rid of the overhead" Such a bold statement cannot be made without profiling, have you profiled your code before and after?Rossanarosse
No , the source code is huge , i didn't change anything yet , i need to know some answers firstHerminiahermione
Stick with virtualPronto
@iMoses , can you plz clarify the reasons ?Herminiahermione
@DhiaHassen , I never investigated how is std::function implemented, but it's only reasonable it is far less efficient than a virtual call, the std::function is meant for other uses, not for optimizing virtual calls, and the STL as it is puts design first and performance later.Pronto
Im not trying to optimize the virtual calls here , im ready to pay some performance , ( read the note i have added at the end ) , if that paid performance will simplify the engine usage and removes the need to declare a new type each time , so i think in my case , it is ok ?Herminiahermione
I'm certain that for the use case of handling mouse events any performance losses or wins will be neglible. Run a profiler to answer the question yourself. Theoretical SO answers won't bring you forward.Guardian
that was 1 year ago , and i did run the engine's profiler finding that std::function is better because it avoides extra callsHerminiahermione
C
11

It really depends on the usage of the virtual function vs. the function object.

although std::function may be slower than a virtual call*, std::function has short buffer optimization , which may prevent dynamic memory allocation (which you would probably have in the virtual version).
this itself may outperform anything you might do with regular polymorphism.

Internally (but not guaranteed), the std::function object anyway uses a virtual call for the type erasure, so I'd say the difference is negligible.

A tip - make sure to check if the std::function is valid (by calling if(static_cast<bool>(myFunction))). the compiler will insert a check to see if the function is empty. if not, the program will throw std::bad_function_call. the developer check will make the compiler remove his check and the code related to std::bad_function_call when optimizations are turned on, leaving much "smoother" assembly code.

when dealing with performance and C++ in my experience, it's much more important to optimize away memory allocations, inter-thread-contention and bad data structures which work bad with the cache. usually, it's much more worthy optimizations than optimizing CPU-stuff away (like virtual function vs. std::function)

*A decent compiler can implement the type erasure as a virtual function + the given lambda as inlined function. theoretically, it should not be slower than a regular virtual function. on the other hand, if the function object gets a non-inlinable callable, like a function pointer, it may use two indirection in order to launch the function. this may be slower than a regular virtual call. it depends.

Canopus answered 18/12, 2016 at 13:39 Comment(3)
I'm having the same dilema as the OP right now, but since overriding OnMouseDown is normally optional, if you have 1000 objects, and only 200 of them overwrite it, you will make 1000 calls to the virtual OnMouseDown. However, if you have a std::function instead (or a plain c function pointer) you can check if it's null, and don't take the call overhead, which should make things faster. Then again branch conditions are semi calls (jumps), but since they are short jumps, if I'm correctly they take like 5 cycles. A virtual call will 90% of the times be a FAR call.Aniakudo
that's why i asked this questionHerminiahermione
Note : you don't need if (static_cast<bool>(myFunction)), because in C++11, there is something called contextual conversions for explicit operator bool() const. It means that in some cases (if, while, &&, ...), the operator becomes implicit and you can just do if (myFunction).Centrist

© 2022 - 2024 — McMap. All rights reserved.