12

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!) ;-)

Benj
  • 31,668
  • 17
  • 78
  • 127
  • 3
    Sounds very dubious to me, and, unless it's running in some performance hotspot, completely irrelevant. – spencercw Feb 28 '12 at 21:39
  • 3
    You never look stupid for asking questions. If he thinks you are then HE IS STUPID. Asking is how we better ourselfs. – Iznogood Feb 28 '12 at 21:39
  • @spencercw: But why make a function a member function if it can be static? Even from a design perspective that makes no sense. – Ed S. Feb 28 '12 at 21:41
  • @EdS.: Are you asserting that there is no place in the world for static member functions? I'm not necessarily disagreeing, but that's a pretty grandiose claim; I'm not sure that there's anywhere near the sort of consensus on that to decide that the alternative "makes no sense" – Lightness Races in Orbit Feb 28 '12 at 21:42
  • @EdS. He says it does have some internal state. If anything making the functions static could make it worse because whatever the state was now has to be recreated for every function call rather than per object. Of course, it's difficult to talk in concrete terms without seeing some code. – spencercw Feb 28 '12 at 21:43
  • @LightnessRacesinOrbit: No, I would avoid absolutes like that :) – Ed S. Feb 28 '12 at 21:44
  • 1
    @spencercw: I was actually under the impression that these would not be member functions at all, but instead free functions. – Ed S. Feb 28 '12 at 21:45
  • @EdS - Yes, that's true, the class would essentially become a namespace with free functions in it. – Benj Feb 28 '12 at 21:48
  • Well, any time you call only (non-static) methods that aren't using the internal state, then you'd be using more stack than necessary (incrementing/decrementing stack pointer unnecessarily) and possibly initialising unnecessarily. Maybe that was the issue? – Elliott Oct 16 '21 at 02:36

6 Answers6

12

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.

doron
  • 27,972
  • 12
  • 65
  • 103
  • This is more or less what I understood to be the case. In my case, as I said in my question, there are no vtables to deal with and no dynamic memory allocation. So really, the only extra cost is the implicit this pointer passing. Which given that the code isn't performance critical probably means that the change wouldn't even be measurable in performance terms. However, probably the right thing to do from a style/design point of view. – Benj Feb 29 '12 at 09:31
  • I know this is an old post, but `In C++ the cost of instantiating an object is the same as instantiating a struct in C` - except objects in C++ might have non-default constructors, and are often nested (so even if the "top" type has only the default constructor, types that it uses might not). None-the-less, if there's an issue with instantiating the `class`, then they'd be the same issue with instantiating the equivalent `struct` then sending it to the static methods. – Elliott Oct 16 '21 at 02:14
6

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.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • 2
    I think the idea is that the class would become uninstantiable; i.e. he really should be using a namespace instead. – Lightness Races in Orbit Feb 28 '12 at 21:40
  • @LightnessRacesinOrbit: Yeah, if it is *only* a set of static functions than the class is not needed, but the OP said that the class did in fact maintain "minimal" amounts of state. – Ed S. Feb 28 '12 at 21:43
  • @EdS.: ... and that it was suggested that that state could instead be derived when needed – Lightness Races in Orbit Feb 28 '12 at 21:48
  • The issue though is the OP said that a non-trivial constructor is necessary, therefore there is most likely *some* type of state variable per-instance, rather than a global state. Otherwise why would a non-trivial constructor be necessary? So if that is the case, then there are two issues ... methods that must access the local state, and methods that don't need to. Those that don't need to can be `static`, but those that must will either have to pass the instance by reference or pointer, negating the benefit of making them static from a call-efficiency perspective. – Jason Feb 28 '12 at 21:53
4

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".

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
3

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:

  • user input
  • network calls
  • database access
  • computation intensive algos
  • thread switching costs
  • system calls

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?

Sid
  • 7,511
  • 2
  • 28
  • 41
2

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?

Ed S.
  • 122,712
  • 22
  • 185
  • 265
1

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

Bowie Owens
  • 2,798
  • 23
  • 20
  • 2
    Hmm, I was aware that inlined comparator functions could dramatically effect performance of sort etc but I was unaware that a functor was any more likely to be inlined than a free function. Can you explain why this is? – Benj Feb 29 '12 at 09:25
  • 1
    Hmm, sounds like this inlining candidacy is related more to using templates than using member functions. Good explanation here: http://stackoverflow.com/questions/8925177/c-templates-for-performance – Benj Feb 29 '12 at 13:16
  • Benj it is not about templates but about levels of abstraction that the compiler can and can't work with. Please let me know if I can provide any further clarification. Also do watch the talk by Bjarne Stroustrup it is very interesting. – Bowie Owens Feb 29 '12 at 23:19