views:

1894

answers:

16

I've read all the advice on const-correctness in C++ and that it is important (in part) because it helps the compiler to optimize your code. What I've never seen is a good explanation on how the compiler uses this information to optimize the code, not even the good books go on explaining what happens behind the curtains.

For example, how does the compiler optimize a method that is declared const vs one that isn't but should be. What happens when you introduce mutable variables? Do they affect these optimizations of const methods?

+6  A: 

handwaving begins

Essentially, the earlier the data is fixed, the more the compiler can move around the actual assignment of the data, ensuring that the pipeline doesn't stall out

end handwaving

Paul Nathan
+6  A: 

It does not optimize the function that is declared const.

It can optimize functions that call the function that is declared const.

void someType::somefunc();

void MyFunc()
{
    someType A(4);   // 
    Fling(A.m_val);
    A.someFunc();
    Flong(A.m_val);
}

Here to call Fling, the valud A.m_val had to be loaded into a CPU register. If someFunc() is not const, the value would have to be reloaded before calling Flong(). If someFunc is const, then we can call Flong with the value that's still in the register.

James Curran
I don't think const helps with optimization at all. A.someFunc() could easily doconst_cast<someType*>(this)->m_val = 42;or any number of other things that would change m_val (e.g. if there is a non-const pointer to A in a global variable, someFunc could change it)
Qwertie
The compiler is allowed to assume you won't cast away const; you do so at your own risk. And, as A lives entirely within MyFunc(), the compiler can track if there's a global pointer to it. (In this example, there can't be)
James Curran
A: 

What Vlion said, and...

Small const parameters such as pointers and integers can be stored in registers for the scope of the function rather than accessed via the stack, where registers are alot faster.

Shane MacLaughlin
Umm, I think you're confusing non-const with volatile, or aliasing, or something. Compilers happily keep non-const variables in registers.
+2  A: 

The main reason for having methods as const is for const correctness, not for possible compilation optimization of the method itself.

If variables are const they can (in theory) be optimized away. But only is the scope can be seen by the compiler. After all the compiler must allow for them to be modified with a const_cast elsewhere.

Andrew Stein
+4  A: 

Meh. Const-correctness is more of a style / error-checking thing than an optimisation. A full-on optimising compiler will follow variable usage and can detect when a variable is effectively const or not.

Added to that, the compiler cannot rely on you telling it the truth - you could be casting away the const inside a library function it doesn't know about.

So yes, const-correctness is a worthy thing to aim for, but it doesn't tell the compiler anything it won't figure out for itself, assuming a good optimising compiler.

+2  A: 

I would be surprised if the optimizer actually puts much stock into a const declaration. There is a lot of code that will end up casting const-ness away, it would be a very reckless optimizer that relied on the programmer declaration to assume when the state may change.

Rob Walker
It would be a very recless programmer to cast away constness. What would she be thinking, "naaah, the documentation doesnt really mean that it's immutable. It doesn't apply to _me_"?
gnud
@gnud: Irrelevant - the compiler can't assume the programmer isn't doing it.
@Mike F: Extremely relevant, the compiler CAN assume the programmer isn't doing it. The standard states (7.1.6.1/4) that: "Except that any class member declared mutable can be modified, any attempt to modify a const object during its lifetime results in undefined behavior."
Nick Lewis
+16  A: 

I think that the const keyword was primarily introduced for compilation checking of the program semantic, not for optimization.

Herb Sutter, in the GotW #81 article, explains very well why the compiler can't optimize anything when passing parameters by const reference, or when declaring const return value. The reason is that the compiler has no way to be sure that the object referenced won't be changed, even if declared const : one could use a const_cast, or some other code can have a non-const reference on the same object.

However, quoting Herb Sutter's article :

There is [only] one case where saying "const" can really mean something, and that is when objects are made const at the point they are defined. In that case, the compiler can often successfully put such "really const" objects into read-only memory[...].

There is a lot more in this article, so I encourage you reading it: you'll have a better understanding of constant optimization after that.

Luc Touraille
+1  A: 

Weird effects of const and const_cast, what does the following output (using VC2005 release build):

#include <iostream>
using namespace std;

void swap (const int &a, const int &b)
{
  int
    c = a;
  const_cast <int&>(a) = b;
  const_cast <int&>(b) = c;
}

void main ()
{
  const int a = 10;
  const int b = 20;
  int c = 30;
  swap (a,b);
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  swap (c,b);
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
}

Skizz

Skizz
pretty cool. You should make main return int though.
David Nehme
Unfortunately, your program is incorrect. You're casting away the const-ness of data that the compiler is free to put in read-only memory. Hopefully, your program aborts itself at runtime.
Tom
A: 

The output to the code in the previous post is as follows:

a = 10;
b = 20;

OK so far, a and b are declared constant so their values shouldn't really change.

b = 20;

If the first swap didn't change a or b, the second swap shouldn't change b.

c = 10;

WTF! Where did that one come from?

Skizz

Skizz
The compiler is told in main() that a and b are constants. It therefore feels free to optimize the print expression, replacing a and b with 10 and 20, respectively. I believe this is mandated by the standard. When the compiler gets to printing c, however, it does not replace it with 30, because c is not const. Instead it uses the actual value of c, which is 10 (after the swap()s). The compiler doesn't trust the constness of the arguments passed to swap() precisely because const_cast can undo it.
Ari
To see that swap() really does swap a and b and b and c, you can add the following:const int *pa = const int *pb = cout << "a = " << *pa << endl;cout << "b = " << *pb << endl;
Ari
+1  A: 

Have you seen these manuals? http://www.agner.org/optimize/#manuals

akalenuk
Great resource, thank you!
David Holm
A: 

The most obvious point where const is a direct optimization is in passing arguments to a function. It's often important to ensure that the function doesn't modify the data so the only real choices for the function signature are these:

void f(Type dont_modify); // or
void f(Type const& dont_modify);

Of course, the real magic here is passing a reference rather than creating an (expensive) copy of the object. But if the reference weren't marked as const, this would weaken the semantics of this function and have negative effects (such as making error-tracking harder). Therefore, const enables an optimization here.

/EDIT: actually, a good compiler can analyze the control flow of the function, determine that it doesn't modify the argument and make the optimization (passing a reference rather than a copy) itself. const here is merely a help for the compiler. However, since C++ has some pretty complicated semantics and such control flow analysis can be very expensive for big functions, we probably shouldn't rely on compilers for this. Does anybody have any data to back me up / prove me wrong?

/EDIT2: and yes, as soon as custom copy constructors come into play, it gets even trickier because compilers unfortunately aren't allowed to omit calling them in this situation.

Konrad Rudolph
Either your build tools need link-time optimization, or your copy constructor (and possibly destructor) need to be visible to the compiler at the call site. Otherwise, the compiler will not be able to optimize out a copy.
Tom
+1  A: 

This code,

class Test
{
public:
  Test (int value) : m_value (value)
  {
  }

  void SetValue (int value) const
  {
    const_cast <Test&>(*this).MySetValue (value);
  }

  int Value () const
  {
    return m_value;
  }

private:
  void MySetValue (int value)
  {
    m_value = value;
  }

  int
    m_value;
};

void modify (const Test &test, int value) 
{
  test.SetValue (value);
}

void main ()
{
  const Test
    test (100);

  cout << test.Value () << endl;
  modify (test, 50);
  cout << test.Value () << endl;
}

outputs:

100
50

which means the const declared object has been altered in a const member function. The presence of const_cast (and the mutable keyword) in the C++ language means that the const keyword can't aide the compiler in generating optimised code. And as I pointed out in my previous posts, it can even produce unexpected results.

As a general rule:

const != optimisation

In fact, this is a legal C++ modifier:

volatile const

Skizz

Skizz
+5  A: 

Let's disregard methods and look only at const objects; the compiler has much more opportunity for optimization here. If an object is declared const, then (ISO/IEC 14882:2003 7.1.5.1(4)):

Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.

Lets disregard objects that may have mutable members - the compiler is free to assume that the object will not be modified, therefore it can produce significant optimizations. These optimizations can include things like:

  • incorporating the object's value directly into the machines instruction opcodes
  • complete elimination of code that can never be reached because the const object is used in a conditional expression that is known at compile time
  • loop unrolling if the const object is controlling the number of iterations of a loop

Note that this stuff applies only if the actual object is const - it does not apply to objects that are accessed through const pointers or references because those access paths can lead to objects that are not const (it's even well-defined to change objects though const pointers/references as long as the actual object is non-const and you cast away the constness of the access path to the object).

In practice, I don't think there are compilers out there that perform any significant optimizations for all kinds of const objects. but for objects that are primitive types (ints, chars, etc.) I think that compilers can be quite aggressive in optimizing the use of those items.

Michael Burr
A: 

Const helps compilers optimize mainly because it makes you write optimizable code. Unless you throw in const_cast.

MSN

MSN
This is unfortunately not true. The compiler has to assume that const_cast and mutable internals will exist elsewhere, unless it can see all of the code in one pass (header-only code).
Tom
+1  A: 

Those are all true answers, but the answers and the question seem to presume one thing: that compiler optimization actually matters.

There is only one kind of code where compiler optimization matters, that is in code that is

  • a tight inner loop,
  • in code that you compile, as opposed to a 3rd-party library,
  • not containing function or method calls (even hidden ones),
  • where the program counter spends a noticeable fraction of its time

If the other 99% of the code is optimized to the Nth degree, it won't make a hoot of difference, because it only matters in code where the program counter actually spends time (which you can find by sampling).

Mike Dunlavey
A: 

const-correctness is also useful as documentation. If a function or parameter is listed as const, I don't need to worry about the value changing out from under my code (unless somebody else on the team is being very naughty). I'm not sure it would be actually worth it if it wasn't built into the library, though.

David Thornley