What Kind of Optimization Does Const Offer in C/C++

What kind of optimization does const offer in C/C++?

Source

Case 1:

When you declare a const in your program,

int const x = 2;

Compiler can optimize away this const by not providing storage for this variable; instead it can be added to the symbol table. So a subsequent read just needs indirection into the symbol table rather than instructions to fetch value from memory.

Note: If you do something like:

const int x = 1;
const int* y = &x;

Then this would force compiler to allocate space for x. So, that degree of optimization is not possible for this case.

In terms of function parameters const means that parameter is not modified in the function. As far as I know, there's no substantial performance gain for using const; rather it's a means to ensure correctness.



Case 2:

"Does declaring the parameter and/or the return value as const help the compiler to generate more optimal code?"

const Y& f( const X& x )
{
// ... do something with x and find a Y object ...
return someY;
}

What could the compiler do better? Could it avoid a copy of the parameter or the return value?

No, as argument is already passed by reference.

Could it put a copy of x or someY into read-only memory?

No, as both x and someY live outside its scope and come from and/or are given to the outside world. Even if someY is dynamically allocated on the fly within f() itself, it and its ownership are given up to the caller.

What about possible optimizations of code that appears inside the body of f()? Because of the const, could the compiler somehow improve the code it generates for the body of f()?

Even when you call a const member function, the compiler can't assume that the bits of object x or object someY won't be changed. Further, there are additional problems (unless the compiler performs global optimization): The compiler also may not know for sure that no other code might have a non-const reference that aliases the same object as x and/or someY, and whether any such non-const references to the same object might get used incidentally during the execution of f(); and the compiler may not even know whether the real objects, to which x and someY are merely references, were actually declared const in the first place.



Case 3:

void f( const Z z )
{
// ...
}

Will there be any optimization in this?

Yes because the compiler knows that z truly is a const object, it could perform some useful optimizations even without global analysis. For example, if the body of f() contains a call like g( &z ), the compiler can be sure that the non-mutable parts of z do not change during the call to g().

Does const-correctness give the compiler more room for optimization?

[Edit: OK so this question is more subtle than I thought at first.]

Declaring a pointer-to-const or reference-of-const never helps any compiler to optimize anything. (Although see the Update at the bottom of this answer.)

The const declaration only indicates how an identifier will be used within the scope of its declaration; it does not say that the underlying object can not change.

Example:

int foo(const int *p) {
int x = *p;
bar(x);
x = *p;
return x;
}

The compiler cannot assume that *p is not modified by the call to bar(), because p could be (e.g.) a pointer to a global int and bar() might modify it.

If the compiler knows enough about the caller of foo() and the contents of bar() that it can prove bar() does not modify *p, then it can also perform that proof without the const declaration.

But this is true in general. Because const only has an effect within the scope of the declaration, the compiler can already see how you are treating the pointer or reference within that scope; it already knows that you are not modifying the underlying object.

So in short, all const does in this context is prevent you from making mistakes. It does not tell the compiler anything it does not already know, and therefore it is irrelevant for optimization.

What about functions that call foo()? Like:

int x = 37;
foo(&x);
printf("%d\n", x);

Can the compiler prove that this prints 37, since foo() takes a const int *?

No. Even though foo() takes a pointer-to-const, it might cast the const-ness away and modify the int. (This is not undefined behavior.) Here again, the compiler cannot make any assumptions in general; and if it knows enough about foo() to make such an optimization, it will know that even without the const.

The only time const might allow optimizations is cases like this:

const int x = 37;
foo(&x);
printf("%d\n", x);

Here, to modify x through any mechanism whatsoever (e.g., by taking a pointer to it and casting away the const) is to invoke Undefined Behavior. So the compiler is free to assume you do not do that, and it can propagate the constant 37 into the printf(). This sort of optimization is legal for any object you declare const. (In practice, a local variable to which you never take a reference will not benefit, because the compiler can already see whether you modify it within its scope.)

To answer your "side note" question, (a) a const pointer is a pointer; and (b) a const pointer can equal NULL. You are correct that the internal representation (i.e. an address) is most likely the same.

[update]

As Christoph points out in the comments, my answer is incomplete because it does not mention restrict.

Section 6.7.3.1 (4) of the C99 standard says:

During each execution of B, let L be any lvalue that has &L based on P. If L is used to
access the value of the object X that it designates, and X is also modified (by any means),
then the following requirements apply: T shall not be const-qualified. ...

(Here B is a basic block over which P, a restrict-pointer-to-T, is in scope.)

So if a C function foo() is declared like this:

foo(const int * restrict p)

...then the compiler may assume that no modifications to *p occur during the lifetime of p -- i.e., during the execution of foo() -- because otherwise the Behavior would be Undefined.

So in principle, combining restrict with a pointer-to-const could enable both of the optimizations that are dismissed above. Do any compilers actually implement such an optimization, I wonder? (GCC 4.5.2, at least, does not.)

Note that restrict only exists in C, not C++ (not even C++0x), except as a compiler-specific extension.

Does const allow for (theoretical) optimization here?

The standard says in [dcl.type.cv]:

Except that any class member declared mutable […] can be modified, any attempt to modify […] a const object […] during its lifetime […] results in undefined behavior.

It is also not possible to make this defined by ending the lifetime of the object prematurely, according to [basic.life]:

Creating a new object within the storage that a const complete object with […] automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior.

This means that the optimization of x - y to zero is valid because any attempt to modify x in foo would result in undefined behavior.

The interesting question is if there is a reason for not performing this optimization in existing compilers. Considering that the const object definition is local to test2 and the fact is used within the same function, usual exceptions such as support for symbol interposition do not apply here.

Can const-correctness improve performance?

const correctness can't improve performance because const_cast and mutable are in the language, and allow code to conformingly break the rules. This gets even worse in C++11, where your const data may e.g. be a pointer to a std::atomic, meaning the compiler has to respect changes made by other threads.

That said, it is trivial for the compiler to look at the code it generates and determine if it actually writes to a given variable, and apply optimizations accordingly.

That all said, const correctness is a good thing with respect to maintainability. Otherwise, clients of your class could break that class's internal members. For instance, consider the standard std::string::c_str() -- if it couldn't return a const value, you'd be able to screw around with the internal buffer of the string!

Don't use const for performance reasons. Use it for maintainability reasons.

What other stuff does Const do in C++ other then telling compiler that particular thing is read only

It's probably confusing because it's mixing two styles of const together.

const int*const Method3(const int*const&)const;

I will reorder them because the best way to understand these is to read them backwards, in my opinion.

Let's start with the return type:

const int*const -> int const* const

By reading it backwards we get: const pointer to const int.

Similarly, for the function parameter:

const int* const& -> int const* const&

By reading it backwards we get: reference to const pointer to const int.

The function is also marked as const, which means it is a member function that can be called when a reference to that class is constant, for example.

For possible const optimizations and further understanding, see these answers:

  • https://stackoverflow.com/a/27466684/2296177
  • https://stackoverflow.com/a/3435076/2296177
  • https://stackoverflow.com/a/38925200/2296177

Why does a const int not get optimized by the compiler (through the symbol table) if another pointer points to its reference?

IMHO, Peter provided the explanation in his comment:

If a pointer is initialised to contain the address of a variable, and that pointer can be accessed from another compilation unit, then it would be reasonable for the compiler to allow for the possibility that the pointer IS dereferenced after being initialised in some compilation unit that is not visible to the compiler. One consequence of that is not optimising the pointer or the variable out of existence. There are numerous other reasoning approaches that might lead to the same outcome, depending on what code the compiler can actually see.

and this is exactly what I think too.

The const in C++ is a little bit confusing. It looks like the abbreviation of “constant” but actually it means “read-only”.

This in mind, I never wondered why the following code is legal in C:

enum { N = 3 };
static int a[N]; /* legal in C: N is a constant. */

but this not:

const int n = 3;
static int b[n]; /* illegal in C: n is a read-only variable */

When I switched to C++, I assumed the above for C++ until I realized in a discussion with a colleague that I was wrong. (Not that this broke any written code of mine, but I hate it to be wrong.) ;-)

const int n = 3;
static int b[n]; // legal in C++

and Const propagation is the technique which makes it legal.

However, even with const propagation const int x; is still a read-only variable which might be addressed.

The OP provided a link about this topic (which might explain it even better than above):

SO: why the size of array as a constant variable is not allowed in C but allowed in C++?

To make this a ful-featured answer I tried to prepare a sample which illustrates the differences:

#include <iostream>

const int x1 = 1;
static const int x1s = 11;
extern const int x1e = 12;

const int x2 = 2;
extern const int *const pX2 = &x2;

const int x3 = 3;
static const int *const pX3 = &x3;

int main()
{
// make usage of values (to have a side-effect)
std::cout << x1;
std::cout << x1s;
std::cout << x1e;
std::cout << x2;
std::cout << pX2;
std::cout << x3;
std::cout << pX3;
// done
return 0;
}

and the outcome of gcc 8.2 with -O3:

; int main()
main:
; {
sub rsp, 8
; // make usage of values (to have a side-effect)
; std::cout << x1;
mov esi, 1
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << x1s;
mov esi, 11
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << x1e;
mov esi, 12
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << x2;
mov esi, 2
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << pX2;
mov esi, OFFSET FLAT:_ZL2x2
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSo9_M_insertIPKvEERSoT_
; std::cout << x3;
mov esi, 3
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSolsEi
; std::cout << pX3;
mov esi, OFFSET FLAT:_ZL2x3
mov edi, OFFSET FLAT:_ZSt4cout
call _ZNSo9_M_insertIPKvEERSoT_
; // done
; return 0;
; }
xor eax, eax
add rsp, 8
ret

and the IMHO most interesting part – the global variables:

; const int x3 = 3;
_ZL2x3:
.long 3
; extern const int *const pX2 = &x2;
pX2:
.quad _ZL2x2
; const int x2 = 2;
_ZL2x2:
.long 2
; extern const int x1e = 12;
x1e:
.long 12
  1. x1, x1s, and pX3 have been optimized away because they are const and not remarked for external linkage.

  2. x1e and pX2 have been allocated because they are remarked for external linkage.

  3. x2 has been allocated because it is referred by pX2 which is remarked for external linkage. (Something from extern may access x2 via pX2.)

  4. x3 was the most tricky one for me. pX3 has been used (in std::cout << pX3;). Although, its value itself is inlined it refers to x3. Furthermore, although the access to x3 (in std::cout << x3;) was inlined as well, the usage of the pointer pX3 initialized with &x3 prevented to optimize this storage away.

Live Demo on godbolt (which has a nice colored dual-view to make it easy to explore)

I did the same with clang 7.0.0 and the outcome was similar.

(I tried it also with msvc v19.15 but I was not able (not patient enough) to evaluate the outcome.)


Concerning 4., I tried additionally:

const int x4 = 4;
static const int *const pX4 = &x4;

and added to main():

  std::cout << x4;
// No: std::cout << pX4;

In this case, there was no storage allocated – neither for x4 nor for pX4. (pX4 was optimized away, leaving no “reference” to x4 which in turn was optimized away as well.)

It's amazing...

What is the importance of making a variable a constant?

Guarantees

If you were a perfect programmer, then sure, just don't change the variable. But six months down the road, when you haven't looked at this file in a long time and need to make a minor change, you might not remember that your variable shouldn't change. And if other code is written with that assumption, it's a recipe for disaster.

This goes tenfold if you're working with people on a project. A comment saying /* plz don't change this variable kthx */ is one thing, but having the compiler enforce that constraint is much harder to miss.

Optimizations

Constants can't be modified. This allows the compiler to do lots of clever things with them. If I write

const int foo = 5;

int some_function() {
return foo;
}

The compiler can just have some_function return 5, because foo will never change. If foo wasn't const, some_function would always have to go read the current value of the variable. Also, if I have

const char foo[] = "Ashton Bennett is a cool C++ programmer";
...
// Somewhere else in the file
const char bar[] = "Ashton Bennett is a cool C++ programmer";

There's no reason to have both of those strings exist. If the compiler can prove that you never take a reference to either of them, it can fold the constants into one, saving space.

C/C++ compiler optimisations: should I prefer creating new variables, re-using existing ones, or avoiding variables altogether?

Good modern compilers generally do not “care” about the names you use to store values. They perform lifetime analyses of the values and generate code based on that. For example, given:

int x = complicated expression 0;
... code using x
x = complicated expression 1;
... code using x

the compiler will see that complicated expression 0 is used in the first section of code and complicated expression 1 is used in the second section of code, and the name x is irrelevant. The result will be the same as if the code used different names:

int x0 = complicated expression 0;
... code using x0
int x1 = complicated expression 1;
... code using x1

So there is no point in reusing a variable for a different purpose; it will not help the compiler save memory or otherwise optimize.

Even if the code were in a loop, such as:

int x;
while (some condition)
{
x = complicated expression;
... code using x
}

the compiler will see that complicated expression is born at the beginning of the loop body and ends by the end of the loop body.

What this means is you do not have to worry about what the compiler will do with the code. Instead, your decisions should be guided mostly by what is clearer to write and more likely to avoid bugs:

  • Avoid reusing a variable for more than one purpose. For example, if somebody is later updating your function to add a new feature, they might miss the fact you have changed the function parameter with a += b; and use a later in the code as if it still contained the original parameter.
  • Do freely create new variables to hold repeated expressions. int sum = a + b; is fine; it expresses the intent and makes it clearer to readers when the same expression is used in multiple places.
  • Limit the scope of variables (and identifiers generally). Declare them only in the innermost scope where they are needed, such as inside a loop rather than outside. The avoids a variable being used accidentally where it is no longer appropriate.

const vs non-const variable with no change in value once assign

A clever compiler can understand that the value of a variable is never changed, thus optimizing the related code, even without the explicit const keyword by the programmer.

As for your second, question, when you mark a variable as const, then the follow might happen: the "compiler can optimize away this const by not providing storage to this variable rather add it in symbol table. So, subsequent read just need indirection into the symbol table rather than instructions to fetch value from memory". Read more in What kind of optimization does const offer in C/C++? (if any).

I said might, because const does not mean that this is a constant expression for sure, which can be done by using constexpr instead, as I explain bellow.


In general, you should think about safer code, rather than faster code when it comes to using the const keyword. So unless, you do it for safer and more readable code, then you are likely a victim of premature optimization.


Bonus:

C++ offers the constexpr keyword, which allows the programmer to mark a variable as what the Standard calls constant expressions. A constant expression is more than merely constant.

Read more in Difference between `constexpr` and `const` and When should you use constexpr capability in C++11?


PS: Constness prevents moving, so using const too liberally may turn your code to execute slower.

C++ what is the point of const keyword?

Non-exhaustive list of reasons:

  1. Software Engineering (SWE). SWE is not just programming, but programming with other people and over time.

    const allows to explicitly express an invariant, which lets you and others reason about the code. As the program becomes bigger, these invariants cannot be just memorized. That's why encoding them in the programming language helps.

  2. Optimization opportunities.

    With the knowledge that certain values will not change, the compiler can make optimizations that would not be possible otherwise. To take this to the max, constexpr means that a value will be known at compile time, not just at run-time. This becomes even more important in potentially multi-threading contexts.

    Example:
    What kind of optimization does const offer in C/C++?

    I leave out whole program analysis which would require a much longer answer and almost certainly is not applicable to generic C++ programs. But whole-program-analysis will allow reasoning of the analyzer or compiler about constness of variables as they get passed between functions, translation units and libraries.



Related Topics



Leave a reply



Submit