Reputation: 32428
I was recently told in a code review (by an older and wiser C++ developer) to rewrite a class I'd written turning it into a set of static methods instead. He justified this by saying that although my object did contain a very small amount of internal state, it could be derived at runtime anyway and if I changed to static methods I'd avoid the cost of insantiating objects all over the place.
I have now made this change but it got me to thinking, what is the cost of instantiation in C++? I'm aware that in managed languages, there's all the cost of garbage collecting the object which would be significant. However, my C++ object was simply on the stack, it didn't contain any virtual methods so there would be no runtime function lookup cost. I'd used the new C++11 delete mechanism to delete the default copy/assignment operators so there was no copying involved. It was just a simple object with a constructor that did a small amount of work (required anyway with static methods) and a destructor which did nothing. Can anyway tell me what these instation consts would be? (The reviewer is a bit intimidating and I don't want to look stupid by asking him!) ;-)
Upvotes: 13
Views: 7338
Reputation: 3006
As others have suggested talk to your colleague and ask him to explain his reasoning. If practical, you should investigate with a small test program the performance of the two versions. Doing both of these will help you grow as a programmer.
In general I agree with the advice to make a member function static if practical. Not because of performance reasons but because it reduces the amount of context you need to remember to understand the behaviour of the function.
It is worth noting that there is one case where using a member function will result in faster code. That case is when the compiler can perform inlining. This is kind of an advanced topic but it is stuff like that makes it hard to write categorical rules about programming.
#include <algorithm>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <time.h>
bool int_lt(int a, int b)
{
return a < b;
}
int
main()
{
size_t const N = 50000000;
std::vector<int> c1;
c1.reserve(N);
for (size_t i = 0; i < N; ++i) {
int r = rand();
c1.push_back(r);
}
std::vector<int> c2 = c1;
std::vector<int> c3 = c1;
clock_t t1 = clock();
std::sort(c2.begin(), c2.end(), std::less<int>());
clock_t t2 = clock();
std::sort(c3.begin(), c3.end(), int_lt);
clock_t t3 = clock();
std::cerr << (t2 - t1) / double(CLOCKS_PER_SEC) << '\n';
std::cerr << (t3 - t2) / double(CLOCKS_PER_SEC) << '\n';
return 0;
}
On my i7 Linux because g++ can't inline the function int_lt but can inline std::less::operator() the non member function version is about 50% slower.
> g++-4.5 -O2 p3.cc
> ./a.out
3.85
5.88
To understand why such a big difference you need to consider what type the compiler infers for the comparator. In the case int_lt it infers the type bool (*)(int, int) whereas with std::less it infers std::less. With the function pointer the function to be called is only ever known at run time. Which means that it is impossible for the compiler to inline its definition at compile time. In contrast with std::less the compiler has access to the type and its definition at compile time so it can inline std::less::operator(). Which makes a significant difference to performance in this case.
Is this behaviour only related to templates? No, it relates to a loss of abstraction when passing functions as objects. A function pointer does not include as much information as a function object type for the compiler to make use of. Here is a similar example using no templates (well aside from std::vector for convenience).
#include <iostream>
#include <time.h>
#include <vector>
#include <stdlib.h>
typedef long (*fp_t)(long, long);
inline long add(long a, long b)
{
return a + b;
}
struct add_fn {
long operator()(long a, long b) const
{
return a + b;
}
};
long f(std::vector<long> const& x, fp_t const add, long init)
{
for (size_t i = 0, sz = x.size(); i < sz; ++i)
init = add(init, x[i]);
return init;
}
long g(std::vector<long> const& x, add_fn const add, long init)
{
for (size_t i = 0, sz = x.size(); i < sz; ++i)
init = add(init, x[i]);
return init;
}
int
main()
{
size_t const N = 5000000;
size_t const M = 100;
std::vector<long> c1;
c1.reserve(N);
for (size_t i = 0; i < N; ++i) {
long r = rand();
c1.push_back(r);
}
std::vector<long> c2 = c1;
std::vector<long> c3 = c1;
clock_t t1 = clock();
for (size_t i = 0; i < M; ++i)
long s2 = f(c2, add, 0);
clock_t t2 = clock();
for (size_t i = 0; i < M; ++i)
long s3 = g(c3, add_fn(), 0);
clock_t t3 = clock();
std::cerr << (t2 - t1) / double(CLOCKS_PER_SEC) << '\n';
std::cerr << (t3 - t2) / double(CLOCKS_PER_SEC) << '\n';
return 0;
}
Cursory testing indicates that the free function is 100% slower than the member function.
> g++ -O2 p5.cc
> ./a.out
0.87
0.32
Bjarne Stroustrup provided an excellent lecture recently on C++11 which touches on this. You can watch it at the link below.
http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style
Upvotes: 2
Reputation: 28932
Short answer - inherently object allocation is cheap but can get expensive in certain cases.
Long Answer
In C++ the cost of instantiating an object is the same as instantiating a struct in C. All an object is, is a block of memory big enough to store the v-table (if it has one) and all the data attributes. Methods consume no further memory after the v-table has been instantiated.
A non-virtual method is a simple function with an implicit this
as its first parameter. Calling a virtual function is a bit more complicated since it must to a v-table lookup in order to know which function of which class to call.
This means that instantiating a object on the stack involves a simple decrement of the stack pointer (for a full decending stack).
When an object is instantiated on the heap, the cost can go up substantially. But this is something inherent with any heap related allocation. When allocating memory on the heap, the heap needs to find a free block big enough to hold your object. Finding such a block is a non-constant time operation and can be expensive.
C++ has constructors that may allocated more memory for certain pointer data attributes. These are normally heap allocated. This is further compounded if said data members perform heap allocations themselves. This can lead to something involving a substantial number of instructions.
So bottom line is that it depends on how and what the object is that you are instatiating.
Upvotes: 12
Reputation: 32538
If your object-type must invoke a non-trivial constructor and destructor during it's life-time, then the cost is the going to be the minimum cost of creating any C++ object that has a non-trivial constructor and destructor. Making the rest of your methods static
will not reduce that cost. The "price" of space will be at least 1 byte since your class is not a base-class of a derived class, and the only cost-savings in the static
class method calls will be the omission of the implicit this
pointer passed as the hidden first argument of the call, something that would be required for non-static class methods.
If the methods your reviewer is asking you to re-designate as static
never touch the non-static data-members of your class-type, then the passing of the implicit this
pointer is a wasted resource, and the reviewer has a good point. Otherwise, you would have to add an argument to the static methods that would take the class-type as either a reference or pointer, nullifying the gained performance from the omission of the implicit this
pointer.
Upvotes: 6
Reputation: 7631
It depends on what your application does. Is it a real time system on a device with limited memory? If not, most of the time object instantiation won't be an issue, unless you are instantiating millions of these and keeping them around or some weird design like that. Most systems will have a lot more bottlenecks such as:
I think in most cases encapsulation into a class for design trumps small costs of instantiation. Of course there can be those 1% of cases where this doesn't hold but is yours one of those?
Upvotes: 3
Reputation: 124800
As a general rule, if a function can be made static it probably should be. It is cheaper. How much cheaper? That depends on what the object does in its constructor, but the base cost of constructing a C++ object is not that high (dynamic memory allocation of course is more expensive).
The point is not to pay for that which you do not need. If a function can be static, why make it a member function? It makes no sense to be a member function in that case. Will the penalty of creating an object kill the performance of your application? Probably not, but again, why pay for what you don't need?
Upvotes: 2
Reputation: 385405
Probably not a lot, and I'd be amazed if it were any sort of bottleneck. But there's the principle of the thing if nothing else.
However, you should ask the guy; never be afraid to do that, and it's not entirely clear here that losing the stored state and instead deriving it each time (if that's what you're doing instead) is not going to make things worse. And, if it's not, you'd think a namespace would be better than static methods.
A testcase/example would make this easier to answer categorically, further than "you should ask him".
Upvotes: 4