This is a pretty basic question but I'm still unsure:
If I have a class that will be instantiated millions of times -- is it advisable not to derive it from some other class? In other words, does inheritance carry some cost (in terms of memory or runtime to construct or destroy an object) that I should be worrying about in practice?
Example:
class Foo : public FooBase { // should I avoid deriving from FooBase?
// ...
};
int main() {
// constructs millions of Foo objects...
}
Expectations for an inheritance's size have to be realistic. The Federal Reserve's 2019 Survey of Consumer Finances (SCF) found that the average inheritance in the U.S. is $110,050.
What Is the Federal Inheritance Tax Rate? There is no federal inheritance tax—that is, a tax on the sum of assets an individual receives from a deceased person. However, a federal estate tax applies to estates larger than $11.7 million for 2021 and $12.06 million for 2022.
Inheritances are not considered income for federal tax purposes, whether you inherit cash, investments or property. However, any subsequent earnings on the inherited assets are taxable, unless it comes from a tax-free source.
Most of the time, you calculate the cost basis for inherited stock by determining the fair market value of the stock on the date that the person in question died. Sometimes, however, the person's estate may choose what's known as the alternate valuation date, which is six months after the date of death.
Inheriting from a class costs nothing at runtime.
The class instances will of course take up more memory if you have variables in the base class, but no more than if they were in the derived class directly and you didn't inherit from anything.
This does not take into account virtual
methods, which do incur a small runtime cost.
tl;dr: You shouldn't be worrying about it.
i'm a bit surprised by some the responses/comments so far...
does inheritance carry some cost (in terms of memory)
Yes. Given:
namespace MON {
class FooBase {
public:
FooBase();
virtual ~FooBase();
virtual void f();
private:
uint8_t a;
};
class Foo : public FooBase {
public:
Foo();
virtual ~Foo();
virtual void f();
private:
uint8_t b;
};
class MiniFoo {
public:
MiniFoo();
~MiniFoo();
void f();
private:
uint8_t a;
uint8_t b;
};
class MiniVFoo {
public:
MiniVFoo();
virtual ~MiniVFoo();
void f();
private:
uint8_t a;
uint8_t b;
};
} // << MON
extern "C" {
struct CFoo {
uint8_t a;
uint8_t b;
};
}
on my system, the sizes are as follows:
32 bit:
FooBase: 8
Foo: 8
MiniFoo: 2
MiniVFoo: 8
CFoo: 2
64 bit:
FooBase: 16
Foo: 16
MiniFoo: 2
MiniVFoo: 16
CFoo: 2
runtime to construct or destroy an object
additional function overhead and virtual dispatch where needed (including destructors where appropriate). this can cost a lot and some really obvious optimizations such as inlining may/can not be performed.
the entire subject is much more complex, but that will give you an idea of the costs.
if the speed or size is truly critical, then you can often use static polymorphism (e.g. templates) to achieve an excellent balance between performance and ease to program.
regarding cpu performance, i created a simple test which created millions of these types on the stack and on the heap and called f, the results are:
FooBase 16.9%
Foo 16.8%
Foo2 16.6%
MiniVFoo 16.6%
MiniFoo 16.2%
CFoo 15.9%
note: Foo2 derives from foo
in the test, the allocations are added to a vector, then deleted. without this stage, the CFoo was entirely optimized away. as Jeff Dege posted in his answer, allocation time will be a huge part of this test.
Pruning the allocation functions and vector create/destroy from the sample produces these numbers:
Foo 19.7%
FooBase 18.7%
Foo2 19.4%
MiniVFoo 19.3%
MiniFoo 13.4%
CFoo 8.5%
which means the virtual variants take over twice as long as the CFoo to execute their constructors, destructors and calls, and MiniFoo is about 1.5 times faster.
while we're on allocation: if you can use a single type for your implementation, you also reduce the number of allocations you must make in this scenario because you can allocate an array of 1M objects, rather than creating a list of 1M addresses and then filling it with uniquely new'ed types. of course, there are special purpose allocators which can reduce this weight. since allocations/free times are the weight of this test, it would significantly reduce the time you spend allocating and freeing objects.
Create many MiniFoos as array 0.2%
Create many CFoos as array 0.1%
Also keep in mind that the sizes of MiniFoo and CFoo consume 1/4 - 1/8 the memory per element, and a contiguous allocation removes the need to store pointers to dynamic objects. You could then keep track of the object in an implementation more ways (pointer or index), but the array can also significantly reduce allocation demends on clients (uint32_t vs pointer on a 64 bit arch) -- plus all the bookkeeping required by the system for the allocations (which is significant when dealing with so many small allocations).
Specifically, the sizes in this test consumed:
32 bit
267MB for dynamic allocations (worst)
19MB for the contiguous allocations
64 bit
381MB for dynamic allocations (worst)
19MB for the contiguous allocations
this means that the required memory was reduced by more than ten, and the times spent allocating/freeing is significantly better than that!
Static dispatch implementations vs mixed or dynamic dispatch can be several times faster. This typically gives the optimizers more opportunuities to see more of the program and optimize it accordingly.
In practice, dynamic types tend to export more symbols (methods, dtors, vtables), which can noticably increase the binary size.
Assuming this is your actual use case, then you can improve the performance and resource usage significantly. i've presented a number of major optimizations... just in case somebody believes changing the design in such a way would qualify as 'micro'-optimizations.
Largely, this depends upon the implementation. But there are some commonalities.
If your inheritance tree includes any virtual functions, the compiler will need to create a vtable for each class - a jump table with pointers to the various virtual functions. Every instance of those classes will carry along a hidden pointer to its class's vtable.
And any call to a virtual function will involve a hidden level of indirection - rather than jumping to a function address that had been resolved at link time, a call will involve reading the address from the vtable and then jumping to that.
Generally speaking, this overhead isn't likely to be measurable on any but the most time-critical software.
OTOH, you said you'd be instantiating and destroying millions of these objects. In most cases, the largest cost isn't constructing the object, but allocating memory for it.
IOW, you might benefit from using your own custom memory allocators, for the class.
http://www.cprogramming.com/tutorial/operator_new.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With