Reputation:
I'm just wondering how
void test()
{
const static int ABC = 12;
cout << ABC;
}
is different from
void test()
{
const int ABC = 12;
cout << ABC;
}
if this function is repeatedly called while the program is executing? What I mean is, is there any performance difference? Or is there a reason why you should prefer one over the other?
Upvotes: 38
Views: 13466
Reputation: 66
You can also get a good idea of what the compiler will do by making use of your compiler's assembler or something like this online utility: https://godbolt.org and taking a look at the resulting assembly code.
This was worth a look, since the question is fundamental.
An example based on your question, looking at primitives vs "complex" (not even very) considering local vs static type declaration and instantiation:
#include <iostream>
struct non_primitive
{
int v;
non_primitive(const int v_) : v(v_) {}
};
void test_non_static_const_primitive()
{
const int ABC = 12;
std::cout << ABC;
}
void test_static_const_primitive()
{
const static int ABC = 12;
std::cout << ABC;
}
void test_non_static_constant_non_primitive_global_struct()
{
const non_primitive s(12);
std::cout << s.v;
}
void test_non_static_constant_non_primitive_local_struct()
{
struct local_non_primitive
{
int v;
local_non_primitive(const int v_) : v(v_) {}
};
const local_non_primitive s(12);
std::cout << s.v;
}
void test_static_constant_non_primitive_global_struct()
{
const static non_primitive s(12);
std::cout << s.v;
}
void test_static_constant_non_primitive_local_struct()
{
struct local_non_primitive
{
int v;
local_non_primitive(const int v_) : v(v_) {}
};
const static local_non_primitive s(12);
std::cout << s.v;
}
The compiler recognizes that it doesn't have to deal with any memory linkage for local const complex types, and the first four cases are treated identically. It looks like the compiler doesn't try to explore the complex type's relative simplicity and potential for further simplification in the static instantiation.
The resulting cost difference is relatively huge.
The compiled assembly code using gcc7.2 with -O3 optimization:
test_non_static_const_primitive():
mov esi, 12
mov edi, OFFSET FLAT:std::cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_const_primitive():
mov esi, 12
mov edi, OFFSET FLAT:std::cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_non_static_constant_non_primitive_global_struct():
mov esi, 12
mov edi, OFFSET FLAT:std::cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_non_static_constant_non_primitive_local_struct():
mov esi, 12
mov edi, OFFSET FLAT:std::cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_constant_non_primitive_global_struct():
movzx eax, BYTE PTR guard variable for test_static_constant_non_primitive_global_struct()::s[rip]
test al, al
je .L7
mov esi, DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip]
mov edi, OFFSET FLAT:std::cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
.L7:
sub rsp, 8
mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_global_struct()::s
call __cxa_guard_acquire
test eax, eax
mov esi, DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip]
je .L8
mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_global_struct()::s
mov DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip], 12
call __cxa_guard_release
mov esi, 12
.L8:
mov edi, OFFSET FLAT:std::cout
add rsp, 8
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_constant_non_primitive_local_struct():
movzx eax, BYTE PTR guard variable for test_static_constant_non_primitive_local_struct()::s[rip]
test al, al
je .L14
mov esi, DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip]
mov edi, OFFSET FLAT:std::cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
.L14:
sub rsp, 8
mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_local_struct()::s
call __cxa_guard_acquire
test eax, eax
mov esi, DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip]
je .L15
mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_local_struct()::s
mov DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip], 12
call __cxa_guard_release
mov esi, 12
.L15:
mov edi, OFFSET FLAT:std::cout
add rsp, 8
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
_GLOBAL__sub_I__Z31test_non_static_const_primitivev:
sub rsp, 8
mov edi, OFFSET FLAT:std::__ioinit
call std::ios_base::Init::Init()
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:std::__ioinit
mov edi, OFFSET FLAT:std::ios_base::Init::~Init()
add rsp, 8
jmp __cxa_atexit
Upvotes: 2
Reputation: 91
The static const example certainly saves on execution time for subsequent calls, much faster for complex object construction to use static const, but I question the need to limit the ABC to function scope and introduce a variation in behaviour on the function from 1st call to following calls. Usually a file contains coupled functionally coupled functions, just give ABC file scope and be done with it.
Upvotes: -1
Reputation: 2670
It depends on the compiler.
In embedded software, a static const will typically be stored in flash (i.e. code memory), and will be accessed directly, like a normal variable, without any need for initialisation.
In contrast, a non-static const may have its value stored in flash, but the const itself will be created on the stack like a variable, and be initialised just like a variable.
If this is how your compiler handles these scenarios, then the static const is more efficient, as it requires neither stack allocation nor initialisation.
Obviously, these scenarios may be handled different by non-embedded compilers.
Upvotes: 2
Reputation: 248199
There are a couple of things that affect the answer:
const
it will almost certainly be optimized out in any case. That means that the resulting code will most likely be the same.static
members are stored elsewhere which means less locality and probably a cache miss.So the answer is, in cases simple enough for the compiler to figure them out and optimize, it makes zero difference. In your example that would almost certainly be the case.
As long as the variable has a type that is easy and cheap to construct, prefer non-static to avoid the cache miss.
If the type is expensive to construct, you might want to use static
.
And of course, last, and most importantly of all:
Don't trust our guesswork. If you are concerned about performance, there is only one correct course of action:
Upvotes: 56
Reputation: 10129
For a basic type, such as an integer value, then I would pigeon hole the use of static as a "premature optimisation" unless you have done some benchmarking and taken into account the various trade offs (for example initialising a static to a non-zero value often requires an entry in a table to specify the position, size and initial value to be set when the code is loaded).
Unless you are taking a pointer to the int and it is dereferenced after your function returns then you don't need the static - let the compiler do the optimisation.
If a pointer to the value is dereferenced after your function has exited then I would class it as a persistent state variable and it would be good practice to define it at at the class or module level to make that clear.
Upvotes: 1
Reputation: 99665
In first cast ABC will be initialized only once, on first function call. In second case ABC will be initialized every time. You'll feel the difference if ABC is complex type with constructor. It could allocate memory or initialize some mutex. For int there is no difference in practice.
According to C++03 Standard 3.7.1/2:
If an object of static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy may be eliminated as specified in 12.8.
Upvotes: 2
Reputation: 986
I would go with the second one - it is more readable. why unnecesarily add a keyword (static) that does not really add any value to someone reading the code.
Upvotes: -1
Reputation: 62333
well theoretically the former method is a tiny bit better as it only insantiates the variable once.
However the compiler will simply remove either "const" line and substitute "cout << 12;" into the function (When compiled with optimisations on, obviously).
Upvotes: 2