News:

11 March 2016 - Forum Rules

Main Menu

C++ Virtual Stigmas

Started by Rhys, November 30, 2012, 02:26:09 AM

Previous topic - Next topic

Rhys

There seems to be a lot of stigma attached to using virtual methods and polymorphic class hierarchies in C++. Even Bjarne Stroustrup states that templates are better than polymorphism.

A lot of the issue seems to be around there being an extra lookup in the vtable per call, on stackoverflow they worked this out to around 100 extra cycles per call. On today's hardware is this really even an issue anymore though?

The other reason I've seen mentioned is the possibility of a cache miss, although I'm a bit fuzzy about what that actually is/means for a program. My best guess is the method isn't in the cpu's cache so it has to be loaded from RAM instead.

I come from an OO background, so a flat class structure just irks me, I always strive to make as much of my code reusable as possible, and with a flat class structure that seems to be considerably harder to achieve.

LostTemplar

#1
Polymorphism and templates are two very different beasts, even if their uses overlap somewhat, so you can't really say that one is 'better' than the other. Usually, I'd say polymorphism is about the how and templates are about the what, but by combining both you can actually achieve some very cool things.

Now, about the overhead introduced by virtual function calls, I'd say unless you're writing some critical code where you have to squeeze every ounce of performance out of it, you can safely ignore it. The architectural benefits outweigh the performance loss by far - just think about it, OO is pretty much useless without polymorphism. Furthermore, a lot of modern C++ libraries use polymorphism rigorously and still are quite fast (e.g. VTK, ITK, to name two I've used a lot). If your program is a bit lacking in the speed department there are other aspects that you should probably attend to first: Are there faster algorithms? Is the overall design sound? Etc.

Also, I'm not sure if you're coming from another OO-language, but I wouldn't think that they don't have any overheads. It's just not possible, the compiler has to look somewhere what function to actually call.

Rhys

That's exactly my thoughts on the issue too. It seems everywhere I turn online I get people saying never use virtual functions or inheritance, because it's slow. Personally I think that's a bit ridiculous, what's the point of having an object oriented language if you don't use one of its primary features?

FAST6191

Now, as far as actual applied C family programming goes, my grasp of the concepts being discussed here is tenuous at best so I would not spend too long pondering my words here but unlike say goto statements would not the main times for "abuse" be among those playing in obfuscation circles? I might possibly be able to listen to complaints on code portability, legibility (although not understanding inheritance is usually viewed similarly to not understanding pointers from what I have seen) and maybe binary size (although in a world where most just have to mash libraries together.....) but this seems off target. Furthermore in a world of the sorts of pipelining, compiler optimisation/modern compilers and branch prediction we see today* there are surely far better targets.

*there might be something to be said if we are looking at the rather more simplistic ARM type and embedded type devices compared to say X86 or something like that.

Going for other seemingly dissonant lines of thought are these not usually the very same people that would chastise you no end for having a main loop longer than it possibly could be (even if the "extra" is a seldom used branch and ultimate act of the program)?

Rhys

I'd imagine so, yeah. There seems to be a hell of a lot of C++ purists out there. ::)

snarfblam

"Premature optimization is the root of all evil."

A programmer should focus on things like profiling code and better algorithms as far as performance goes. Code readability and maintainability should be higher priorities yet, so that you can go back and optimize and fix bugs once you know where the actual problem is. Shaving a few cycles here and there is silly. Only a tiny fraction of your code gets run often enough to warrant any kind of concern over speed. And, on a personal level, if my code is harder to understand and maintain, I'm much less likely to finish or update a project I'm working on.

furrykef

#6
I always laugh when people complain about the performance of virtual functions and whatnot. I code in Python, where every function is virtual (even those that aren't inside a class). My code runs fine. Virtual functions in C++ are not going to be a problem unless you're calling them in a tight loop or something, and if you've got a tight loop with a performance problem, you'll know it and be able to do something about it then.

Most people who worry about performance really shouldn't, even if they're writing something like a video game. I wrote a video game entirely in Python and it ran much faster than it needed to, even on my old computer. Granted, it was a 2D game. If you're writing software for cell phones or something, you have reason to worry, because slow code will hog the CPU, which drains the battery, but otherwise...

Now, sometimes inheritance and virtual functions are inappropriate in C++ for other reasons. One is that composition or templates may be a better solution to your specific problem. Also, to use inheritance correctly, you need to know the Liskov Substitution Principle, which says that if class B derives from class A, then it should be the case that A "is a" B. Sometimes this is counterintuitive. For example, a Rectangle "is a" Shape. But a Square does not satisfy "Square 'is a' Rectangle", even though you'd expect it to, because a square has certain restrictions a rectangle does not. If you change a rectangle's width, it won't change its height -- but a square would have to, meaning a square doesn't always behave like a rectangle. Code that takes a reference to a Rectangle might not anticipate this behavior.

Rhys

Yeah, I didn't have the name for it but that's a principle I always follow. Any special behavior is always at the bottom of a class tree, and clearly documented in comments.

I'm working on a game engine I'm porting from C#, and I've been trying to replicate some of the nicer behavior of XNA in my ported engine. A lot of this can easily be done with templates, which I just think of as compiled C# generics, and polymorphism. It always concerns me when I see so many people advocating against the very features of the language I use most.

I think unless the platforms being targeted are extremely low on resources and speed, it's probably not worth adding more complexity to your code for little gain on modern machines.

henke37

Remember that the code generated for these features may be better designed than what you would replace it with.

relminator

Quote from: Rhys on November 30, 2012, 05:31:10 AM
That's exactly my thoughts on the issue too. It seems everywhere I turn online I get people saying never use virtual functions or inheritance, because it's slow. Personally I think that's a bit ridiculous, what's the point of having an object oriented language if you don't use one of its primary features?

Vtable access is slow if not used well (ram latency).  You can however, use virtual functions in just about every class you have and still have a very fast system.  There's a trick I used in my DS game that "batches" my virtual calls so that my engine does no jump from one memory location to another with every function call.

http://rel.phatcode.net/index.php?action=contents&item=Space-Impakto-DS

entity.cpp and just about everything that extends it.


As you can see, it's a bullet hell game running on a 66mhz processor and I use virtual functions everywhere.

Here's another example of it.


#include <cstdio>
#include <vector>

using namespace std;


//--------------------------------------------------------------------
//
//    Entities (No virtual fuctions)
//
//--------------------------------------------------------------------

class Popcorn
{
    public:
    void Update()
    {
        printf( "Popcorn Update \n" );
        Kill();
        Pop();
    }

    void Kill()
    {
        printf( "Popcorn Dies \n" );
    }

    void Pop()
    {
        printf( "Popcorn Pops \n" );
    }
};

class Cannon
{
    public:
    void Update()
    {
        printf( "Cannon Update \n" );
    }
};

class Tentakill
{
    public:
    void Update()
    {
        printf( "Tentakill Update \n" );
        Shoot();
    }

    void Shoot()
    {
        printf( "Tentakill Shoots \n" );
    }
};



//--------------------------------------------------------------------
//
//    Global Entity Container ( Pure Virtual Calls )
//
//--------------------------------------------------------------------

class EntityContainer
{
    public:
    virtual void UpdateAll() = 0;
};


//--------------------------------------------------------------------
//
//    Entity Containers for each entity ( Single Virtual Call for each type )
//
//--------------------------------------------------------------------

class PopcornContainer : public EntityContainer
{
    public:
    PopcornContainer()
    {
        Popcorn Ship;
        for( int i = 0; i < 10; i++ )
            Popcorns.push_back( Ship );
    }

    ~PopcornContainer()
    {
        Popcorns.clear();
    }

    // Virtual Function for all types of entities
    virtual void UpdateAll()
    {
        for( unsigned int i = 0; i < Popcorns.size(); i++ )
            Popcorns[i].Update();
    }

    vector<Popcorn> Popcorns;

};

class CannonContainer : public EntityContainer
{
    public:
    CannonContainer()
    {
        Cannon Ship;
        for( int i = 0; i < 10; i++ )
            Cannons.push_back( Ship );
    }

    ~CannonContainer()
    {
        Cannons.clear();
    }

    virtual void UpdateAll()
    {
        for( unsigned int i = 0; i < Cannons.size(); i++ )
            Cannons[i].Update();
    }

    vector<Cannon> Cannons;

};

class TentakillContainer : public EntityContainer
{
    public:
    TentakillContainer()
    {
        Tentakill Ship;
        for( int i = 0; i < 10; i++ )
            Tentakills.push_back( Ship );
    }

    ~TentakillContainer()
    {
        Tentakills.clear();
    }

    virtual void UpdateAll()
    {
        for( unsigned int i = 0; i < Tentakills.size(); i++ )
            Tentakills[i].Update();
    }

    vector<Tentakill> Tentakills;

};


int main()
{
   

    PopcornContainer Popcorns;
    CannonContainer Cannons;
    TentakillContainer Tentakills;

    vector<EntityContainer*> Entities;

    Entities.push_back( &Popcorns );
    Entities.push_back( &Cannons );
    Entities.push_back( &Tentakills );

   
    // Iterator way iz beddar
    vector<EntityContainer*>::iterator iter;
for( iter = Entities.begin(); iter != Entities.end(); iter++ )
{
    (*iter)->UpdateAll();
}

    return 0;
}




hi FAST!