Thread: inheritance and performance

  1. #1
    Registered User
    Join Date
    Jun 2004
    Posts
    8

    inheritance and performance

    Hi,

    I have some questions about c++ inheritance and performance:

    1) How does single inheritance affect performance? Consider the following example:

    Code:
    class Base
    {
    public:
      virtual void f1();
      virtual void f2();
      ...
    }
    
    class Derived
    {
      public virtual void f1();
      ...
    }
    Class Derived overrides the implementation of f1() and inherits the implementation of f2() from Base. How do calls to f1() and f2() perform on an object of class Derived? That is, if the functions are called millions of times (in a for loop, for instance), is the overhead caused by the function call identical for f1() and f2()? And what about calling f1() and f2() on an object of type Base (compared to calling them on an object of type Derived)?

    Is maybe somebody aware of some good (online) articles that describe how single inheritance affects performance?

    2) I've found some articles that state that multiple inheritance may perform badly, particularly if virtual base classes are involved. What about multiple inheritance without virtual base classes? For example, I could define a pure virtual class only consisting of method signatures (like an interface in java). Then a class could be derived from some base class and this interface (there exist many examples for that in java):

    Code:
    class Base
    {
    public:
      virtual void f1();
      virtual void f2();
      ...
    }
    
    
    class Interface
    {
    public:
      virtual void i1() = 0;
      ...
    }
    
    
    class Derived: public Base, public Interface
    {
    public:
      virtual void i1();
      virtual void f1();
      ...
    }
    How do calls to the functions i1, f1 and f2 perform compared to each other when called on an object of type Derived? And how do calls to f1 and f2 perform when calling them on an object of type Base compared to calling them on an object of type Derived?

    Are there any reasons (not only performance) that argue against this use of multiple inheritance?

    Thanks,

    Michael

  2. #2
    Code Monkey Davros's Avatar
    Join Date
    Jun 2002
    Posts
    812
    >How do calls to f1() and f2() perform on an object of class Derived? That is, if the functions are called millions of times (in a for loop, for instance), is the overhead caused by the function call identical for f1() and f2()?

    I can tell you what I know about the subject if that helps.

    Classes which use virtual methods keep a v-table, which is basically a list of function pointers which point to the actual implementations for that object. Hence, you can safely override virtual methods without worrying about which method will actually be called when an object is cast into one of its bases. Generally a class which is expected to be used to derived others should always have a virtual destructor.

    However, for each virtual method in the class, you add an extra pointer to its memory footprint, i.e. 4 bytes on 32bit machines. So this maybe an issue if you were holding large numbers of objects in memory.

    As for speed, I would suspect it would be implementation dependent. It isn't something I would worry about unless I was interested in shaving microseconds off the time it takes to call methods.

    Why don't you write some test code and try it?
    OS: Windows XP
    Compilers: MinGW (Code::Blocks), BCB 5

    BigAngryDog.com

  3. #3
    Registered User
    Join Date
    Jun 2004
    Posts
    8
    A test program is always a good idea :-)

    Well, I've written one:

    Code:
    #include <iostream>
    #include <ctime>
    
    class Base {
    public:
    	virtual void f1() {
    		int a = 0;
    		a++;
    	}
    
    	virtual void f2() {
    		int a = 0;
    		a++;
    	}
    
    	void f3() {
    		int a = 0;
    		a++;
    	}
    };
    
    class Derived : public Base
    {
    public:
    	void f1() {
    		int a = 0;
    		a++;
    	}
    
    	void f4() {
    		int a = 0;
    		a++;
    	}
    };
    
    class Interface
    {
    public:
    	virtual void i1() = 0;
    };
    
    class MIDerived : public Base, public Interface
    {
    public:
    	void i1() {
    		int a = 0;
    		a++;
    	}
    
    	void f1() {
    		int a = 0;
    		a++;
    	}
    
    	void f4() {
    		int a = 0;
    		a++;
    	}
    };
    
    int main(int argc, char** argv)
    {
    	time_t startTime;
    	time_t endTime;
    
    	int loopSize = 500000000;
    
    	Base* base = new Base();
    	Derived* derived = new Derived();
    	MIDerived* miDerived = new MIDerived();
    
    	/*
    	startTime = time(NULL);
    	for (int i0 = 0; i0 < loopSize; i0++) {
    		base->f1();
    	}
    	endTime = time(NULL);
    	std::cout << "Base->f1(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int j0 = 0; j0 < loopSize; j0++) {
    		base->f2();
    	}
    	endTime = time(NULL);
    	std::cout << "Base->f2(): " << (endTime - startTime) << std::endl;
    	*/
    
    	startTime = time(NULL);
    	for (int i = 0; i < loopSize; i++) {
    		base->f1();
    	}
    	endTime = time(NULL);
    	std::cout << "Base->f1(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int j = 0; j < loopSize; j++) {
    		base->f2();
    	}
    	endTime = time(NULL);
    	std::cout << "Base->f2(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int k = 0; k < loopSize; k++) {
    		base->f3();
    	}
    	endTime = time(NULL);
    	std::cout << "Base->f3(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int l = 0; l < loopSize; l++) {
    		derived->f1();
    	}
    	endTime = time(NULL);
    	std::cout << "Derived->f1(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int m = 0; m < loopSize; m++) {
    		derived->f2();
    	}
    	endTime = time(NULL);
    	std::cout << "Derived->f2(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int n = 0; n < loopSize; n++) {
    		derived->f3();
    	}
    	endTime = time(NULL);
    	std::cout << "Derived->f3(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int o = 0; o < loopSize; o++) {
    		derived->f4();
    	}
    	endTime = time(NULL);
    	std::cout << "Derived->f4(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int p = 0; p < loopSize; p++) {
    		miDerived->i1();
    	}
    	endTime = time(NULL);
    	std::cout << "MIDerived->i1(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int q = 0; q < loopSize; q++) {
    		miDerived->f1();
    	}
    	endTime = time(NULL);
    	std::cout << "MIDerived->f1(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int r = 0; r < loopSize; r++) {
    		miDerived->f2();
    	}
    	endTime = time(NULL);
    
    	std::cout << "MIDerived->f2(): " << (endTime - startTime) << std::endl;
    	startTime = time(NULL);
    	for (int s = 0; s < loopSize; s++) {
    		miDerived->f3();
    	}
    	endTime = time(NULL);
    	std::cout << "MIDerived->f3(): " << (endTime - startTime) << std::endl;
    
    	startTime = time(NULL);
    	for (int t = 0; t < loopSize; t++) {
    		miDerived->f4();
    	}
    	endTime = time(NULL);
    	std::cout << "MIDerived->f4(): " << (endTime - startTime) << std::endl;
    
    	return 0;
    }
    I've compiled the program using Visual Studio 6 and run it 4 times. The output looked as follows:

    Base->f1(): 5
    Base->f2(): 12
    Base->f3(): 4
    Derived->f1(): 5
    Derived->f2(): 6
    Derived->f3(): 4
    Derived->f4(): 3
    MIDerived->i1(): 12
    MIDerived->f1(): 12
    MIDerived->f2(): 6
    MIDerived->f3(): 4
    MIDerived->f4(): 3

    Base->f1(): 6
    Base->f2(): 12
    Base->f3(): 4
    Derived->f1(): 5
    Derived->f2(): 5
    Derived->f3(): 4
    Derived->f4(): 4
    MIDerived->i1(): 12
    MIDerived->f1(): 12
    MIDerived->f2(): 5
    MIDerived->f3(): 4
    MIDerived->f4(): 4

    Base->f1(): 5
    Base->f2(): 12
    Base->f3(): 4
    Derived->f1(): 5
    Derived->f2(): 6
    Derived->f3(): 4
    Derived->f4(): 3
    MIDerived->i1(): 12
    MIDerived->f1(): 12
    MIDerived->f2(): 6
    MIDerived->f3(): 3
    MIDerived->f4(): 4

    Base->f1(): 6
    Base->f2(): 12
    Base->f3(): 3
    Derived->f1(): 6
    Derived->f2(): 5
    Derived->f3(): 4
    Derived->f4(): 4
    MIDerived->i1(): 12
    MIDerived->f1(): 11
    MIDerived->f2(): 6
    MIDerived->f3(): 4
    MIDerived->f4(): 3

    I didn't really trust this test, because I didn't expect any difference between base->f1() and base->f2(). Therefore I've executed the first two function calls twice (see commented part of the program). The output looked as follows:

    Base->f1(): 6
    Base->f2(): 11
    Base->f1(): 6
    Base->f2(): 5
    Base->f3(): 4
    Derived->f1(): 12
    Derived->f2(): 5
    Derived->f3(): 4
    Derived->f4(): 4
    MIDerived->i1(): 6
    MIDerived->f1(): 11
    MIDerived->f2(): 12
    MIDerived->f3(): 4
    MIDerived->f4(): 4

    The execution times of this version are not the same as for the first version (compare for example MIDerived->i1()). This confirmed my assumption, that I shouldn't trust the results of this test program... However, I can't explain this behaviour. Can anybody explain me, what is going wrong here? What would be a more sophisticated way to test the performance than using the "time" function (the result of which for example depends on the CPU load caused by other programs)?

  4. #4
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    If you have a profiler, you can use that to get a more accurate speed rating. I know MSVC6 professional comes with one, not sure if other compilers do though. You could also increase the number of repetitions in the loops, i.e. (unsigned long)(-1) or something, or I've also seen people use QueryPerformanceCounter() or something like that... I'm not sure how it works, but you can look it up.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  5. #5
    vae victus! skorman00's Avatar
    Join Date
    Nov 2003
    Posts
    594
    Are there any reasons (not only performance) that argue against this use of multiple inheritance?
    As some of the others have said, the performance hit is negligable for the most part, when used well and in moderation. If it makes it easier to handle your code, then by all means, go for it. In general, the only field where speed and performance is most crucial is in game developement, and I can assure you that we do not have inheritance and v-tables high on our list of "no no's."

    As for the use of mutliple inheritance, only use it if it makes sense and makes your life easier (the same reason why you would choose any method over others). Be careful with multiple inheritance and virtual functions. This structure will proove problematic if you have virtual functions in base that are overloaded in der3:
    Code:
        base
       /    \
    der1    der2
       \    /
        der3
    Not something that usually comes up, but it may in a form as simple as this, or an extremely complicated one.

  6. #6
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    As a side note, you have to use virtual inheritance to get the particular inheritance tree that skorman00 posted. Not using virtual inheritance would result in two copies of the base.

Popular pages Recent additions subscribe to a feed