Virtual function overhead. How bad is it?

Started by
28 comments, last by GameDev.net 19 years, 1 month ago
Regarding the virtual functions are 25% slower message, this number tells nothing without knowing what you tested. If you wrote a test like this:

struct Tester {  void normalFunction() {}  virtual void virtualFunction() = 0;};struct DerivedTester : Tester {  void virtualFunction() {}};void benchmark(Tester &the_tester) {  // do your test here, if the compiler isn't very clever it  // will not generate optimized versions of this function}


it will only tell you the relative performance between a function call and a virtual function call. Even if it is 100% slower, the entire thing comes down to such a low number of clock cycles compared to any function body you would normally write that it doesn't matter at all.

For most C++ compilers on x86, a virtual call causes an overhead of exactly two instructions, namely an 'inc'/'dec' plus a 'jmp'. As has been said, most CPUs are optimized for the typical code flow in a virtual call because it is shared by most current software. Granted, you shouldn't use virtual functions nested three levels deep if you're blitting pixels, but I've never ever seen a virtual function to be the root of evil, or incurrent any noticable impact at all, when making profiler runs on my projects.

-Markus-
Professional C++ and .NET developer trying to break into indie game development.
Follow my progress: http://blog.nuclex-games.com/ or Twitter - Topics: Ogre3D, Blender, game architecture tips & code snippets.
Advertisement
If you want to know if making a function virtual will hurt your performance, look at how many times you call it per game loop. If it is a low level operation you call it tens of thousands of times per game loop, then you might see a performance hit. Also, look at how big you function is. If it is a large function, any lost performance will be overshadowed by the slowness of the function itself. Frankly, any function that is so critical that it can't be virtual should probably be inlined if at all possible.

On the other hand, for long functions that get called once per game loop, you just wont see a difference. I have virtual functions all over my code, and there has been no significant impact on performance.

Also, use virtual where it makes sense. If you need a function to be virtual, make it virtual. Use regular functions everywhere else.
Awesome! Thanks to all who replied. I know understand much better!

Dave
I just depends how many times a particular function is being called. For example, in a program that I am working on I had a "core" function that was virtual that was being called thousands of times while I was parsing a massive amount of text. After profiling, I saw that this was the bottleneck. When I simply changed the function to no longer be virtual (I had to rework my class design a little bit) I saw a huge difference in performance.
Megan
Quote:Original post by JohnBolton
Quote:Original post by Promit
In terms of concrete costs:

A vtable read costs one pointer dereference. Just one.


Don't forget that it also costs a cache flush/fill.


Why does it take a cache flush/fill?
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Well, I can tell you from experience that in some situations, the difference is noticeable. I had a raytracing engine that I wrote, which used virtual functions and overloaded object types for collision detection (ray/object intersections). Well, I replaced the entire code with a large case statement (which gets converted to a jump table with any recent compiler), and the speed increased tremendously (~10x), due to the amount of times each call was issued. Yes, the code was 10x more sloppy, but the speed increase was well worth it for me.
Quote:Original post by Ready4Dis
the speed increased tremendously (~10x), due to the amount of times each call was issued.


Personally I wouldn't have thought this was even possible! Are you sure you didn't fix up anything else? Maybe you were calling the wrong virtual functions or something befor because I can't see anyway you could get a ten fold increase in speed when taking out virtual functions. As has been said the overhead is just a dereference and a jump so how removing that will give you a 10x speed increase I'm not sure :P Somehow I think you fixed some other major bug in your code along the way which gave you the speed increase. C-Junkie was running a worst case senario (lots of small calls in tight loop) and he got a 25% increase, but you managed to get a 1,000% increase?
Quote:Original post by Ready4Dis
Well, I can tell you from experience that in some situations, the difference is noticeable. I had a raytracing engine that I wrote, which used virtual functions and overloaded object types for collision detection (ray/object intersections). Well, I replaced the entire code with a large case statement (which gets converted to a jump table with any recent compiler), and the speed increased tremendously (~10x), due to the amount of times each call was issued. Yes, the code was 10x more sloppy, but the speed increase was well worth it for me.


That's not relevant to virtual overhead. You essentially forced the functions to get inlined by inlining them yourself. You would've done better to make them seperate, non member functions marked inline and call them. (Even __forceinline if you really wanted to press the issue.)
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Quote:Original post by Ready4Dis
Well, I can tell you from experience that in some situations, the difference is noticeable. I had a raytracing engine that I wrote, which used virtual functions and overloaded object types for collision detection (ray/object intersections). Well, I replaced the entire code with a large case statement (which gets converted to a jump table with any recent compiler), and the speed increased tremendously (~10x), due to the amount of times each call was issued. Yes, the code was 10x more sloppy, but the speed increase was well worth it for me.


10x sounds like a bit of an exaggeration, but if there is any merit to it, it is probably because all the generated code gets put in one place, and this reduces memory paging. When the code is put into functions, the code in the calling function could be megabytes away from the function being called. Worse still, if the calling function calls many functions, each function called could theoretically exist in a different page. This, however, is not unique to virtual functions. Its just as true for regular functions as it is for virtual ones.
Many people have mentioned the cost of virtual functions being related to the table look up, i-cache (for the lookup code) and d-cache (for the table) however you've all overlooked one huge problem which is even worse today than ever. It totally screws with instruction pipelining! Until the CPU gets the address of the function to call from the table lookup which will either be in main memory (real slow) or cache (quite a few cycles) it's not possible for the ALU to start reading and decoding new instructions. If you're writing a game that's seriously limited by development time then virtual functions are great. If you're really thinking about performance then think long and hard before writing virtual.

// Bad virtual function
virtual bool IsEnabled(void) const
{
return mEnabled;
}

// Good virtual function
virtual void LoadsOfStuff(
BigLoadOfStuff &stuff)
{
// doing loads of stuff
}

This topic is closed to new replies.

Advertisement