Sign in to follow this  
ccanan

Is const good for efficiency?

Recommended Posts

What Herb Sutter has to say on the subject

Having a const pointer/reference to an object doesn't mean it is constant, it means you can't modify it, so there aren't as many opportunities for optimisation as might seem.


int x = 0;

void f(const int* p)
{
std::cout << *p << std::endl;
++x;
std::cout << *p << std::endl;
}

int main()
{
f(&x);
}

Share this post


Link to post
Share on other sites
If something is const, mark it as const. Don't bend over backwards to make stuff const when it really isn't.

const-correctness is a very good thing for code quality, and (at least in VS 2003) there _are_ places that the compiler optimizes better when you use const, but in general the compiler will figure things out whether you use const or not.

const is good for efficiency because it will reduce development time by pointing out some kinds of programming mistakes. Less development time means more time left over for profiler-guided optimization at the end.

Share this post


Link to post
Share on other sites
Quote:
Original post by SamLowry
What Herb Sutter has to say on the subject

Having a const pointer/reference to an object doesn't mean it is constant, it means you can't modify it, so there aren't as many opportunities for optimisation as might seem.


int x = 0;

void f(const int* p)
{
std::cout << *p << std::endl;
++x;
std::cout << *p << std::endl;
}

int main()
{
f(&x);
}


Of course, this code is truly terrible. Aliasing like this is, in my humble experience, bad. Assuming I saw no reason to flush the stream twice in f(), I would see no reason, looking at f(), that I couldn't write:
void f(cont int*p)
{
++x;
std::cout << *p << "\n" << *p << std::endl;
}

If I were being a good boy, I'd probably spend a couple hours tracking down why the original author placed the ++x; in between the two outputs. If I was being naughty, I'd just change it and get really confused when the results changed.

But that's beside the point since that's perfectly legal code that the compiler must handle correctly. More relevantly, I remember once turning on icc's "tell me why you didn't vectorize everything" option and in many cases it was because it couldn't prove there was no aliasing even though I knew there wasn't. This is why C99 introduced the "restrict" keyword.

Share this post


Link to post
Share on other sites
Declaring actual variables as const rather than pointers or references gives the compiler more opportunities for optimization and is a good idea for code quality reasons as well. Compilers can definitely take advantage of const declarations on local variables for optimization.

Share this post


Link to post
Share on other sites
Quote:
Original post by mattnewport
Declaring actual variables as const rather than pointers or references gives the compiler more opportunities for optimization and is a good idea for code quality reasons as well. Compilers can definitely take advantage of const declarations on local variables for optimization.


For a local variable, couldn't the compiler just see that it was never modified and do the optimizations regardless of whether or not the variable was const?

Share this post


Link to post
Share on other sites
Quote:
Original post by Deyja
[...]Const isn't an optimization.
except in rare cases.
It doesn't matter either way, though, because you should use it wherever it is appropriate and no more or less.

Share this post


Link to post
Share on other sites
Quote:
Original post by Way Walker
Quote:
Original post by mattnewport
Declaring actual variables as const rather than pointers or references gives the compiler more opportunities for optimization and is a good idea for code quality reasons as well. Compilers can definitely take advantage of const declarations on local variables for optimization.


For a local variable, couldn't the compiler just see that it was never modified and do the optimizations regardless of whether or not the variable was const?


// Evil might cast away the constness of its argument.
void evil (const Vector & p);

void good ()
{
Vector x;
const Vector y;
for (int i = 0; i < std::eleventy_billion; ++i) {
// The compiler cannot assume that "x" won't be modified just because it is
// passed as const; casting the const away here has defined behaviour.
evil(x);
// But here the compiler *can* assume that "y" won't be modified because,
// even if the constness is cast away and it *is* modified, the
// consequences of doing so are undefined.
evil(y);
// The compiler can't move this addition out of the loop because "x" might
// legitimately have its value changed.
do_something(x + y);
// OTOH, this multiplication can be moved out of the loop because even if
// evil changes the value of "y", the consequences are undefined.
do_something_else(y * y);
}
}




(Assuming Vector contains no mutable members.)

Share this post


Link to post
Share on other sites
Quote:
Original post by Nathan Baum
Quote:
Original post by Way Walker
Quote:
Original post by mattnewport
Declaring actual variables as const rather than pointers or references gives the compiler more opportunities for optimization and is a good idea for code quality reasons as well. Compilers can definitely take advantage of const declarations on local variables for optimization.


For a local variable, couldn't the compiler just see that it was never modified and do the optimizations regardless of whether or not the variable was const?

*** Source Snippet Removed ***

(Assuming Vector contains no mutable members.)


<grasping at straws>In most cases I've come across (I don't speak for others) that's not a problem.</grasping at straws>

But I am curious what the standard guarantees in the way of const_cast. Got any chapter and verse that'd be relevant?

Share this post


Link to post
Share on other sites
Quote:
Original post by Way Walker
Quote:
Original post by Nathan Baum
Quote:
Original post by Way Walker
Quote:
Original post by mattnewport
Declaring actual variables as const rather than pointers or references gives the compiler more opportunities for optimization and is a good idea for code quality reasons as well. Compilers can definitely take advantage of const declarations on local variables for optimization.


For a local variable, couldn't the compiler just see that it was never modified and do the optimizations regardless of whether or not the variable was const?

*** Source Snippet Removed ***

(Assuming Vector contains no mutable members.)


<grasping at straws>In most cases I've come across (I don't speak for others) that's not a problem.</grasping at straws>

It may be that your compiler simply doesn't do the optimisation, so you would never have noticed even if you had thought to try consting your local variables.
Quote:

But I am curious what the standard guarantees in the way of const_cast. Got any chapter and verse that'd be relevant?


"7.1.5.1 The cv-qualifiers" has a long example explicitly showing that it's okay to cast away const for a pointer/reference to an object that wasn't allocated as const, but not okay if the object was.


const int ci = 3; // cv-qualified (initialized as required)
ci = 4 // ill-formed: attempt to modify const

int i = 2; // not cv-qualified
const int* cip; // pointer to const int
cip = &i; // OK: cv-qualified access path to unqualified
*cip = 4; // ill-formed: attempt to modify through ptr to const

int* ip;
ip = const_cast<int*>(cip); // cast needed to convert const int* to int*
*ip = 4; // defined: *ip points to i, a non-const object

const int* ciq = new const int (3); // initialized as required
int* iq = const_cast<int*>(ciq); // cast required
*iq = 4; // undefined: modifies a const object

Share this post


Link to post
Share on other sites
Quote:
Original post by Nathan Baum
Quote:
Original post by Way Walker
<grasping at straws>In most cases I've come across (I don't speak for others) that's not a problem.</grasping at straws>

It may be that your compiler simply doesn't do the optimisation, so you would never have noticed even if you had thought to try consting your local variables.


I was actually saying that it's not common for my code to call functions with const arguments in loops.

Quote:

"7.1.5.1 The cv-qualifiers" has a long example explicitly showing that it's okay to cast away const for a pointer/reference to an object that wasn't allocated as const, but not okay if the object was.

*** Source Snippet Removed ***


Thanks, appreciate it.

Share this post


Link to post
Share on other sites
I have a question similar to this one.

Can I pass object references to functions without using the '*' notation and avoid calling any extra 'hidden' methods on the object such as a copy constructor? As I'm sure you all know, in C this is done by the calling routine using the '&' operator and the called routine taking a '*' type. I suppose my problem is more of cosmetics, but things just look messy when I pass the address of, say, an std::vector and I want to index the vector: the syntax becomes (*vector)[i].

Is it enough for the called routine to take a 'const &'? Basically what I want to tell the compiler is that the called function will not modify the object and to not make a copy of the object when passing it. Is there anything built into the C++ language that will guarantee this?

Share this post


Link to post
Share on other sites
Whether or not const can speed things up, I personally find it good practice to favour consts above macros:
static const int THE_REASON = 42;

rather than
#define THE_REASON 42


Also, reading that (nice) linked document makes me think that it is one of those rare cases (const at birth) in which the compiler can toy with ROM. Correct?

Share this post


Link to post
Share on other sites
Quote:
Original post by jorgander
I have a question similar to this one.

Can I pass object references to functions without using the '*' notation and avoid calling any extra 'hidden' methods on the object such as a copy constructor? As I'm sure you all know, in C this is done by the calling routine using the '&' operator and the called routine taking a '*' type. I suppose my problem is more of cosmetics, but things just look messy when I pass the address of, say, an std::vector and I want to index the vector: the syntax becomes (*vector)[i].

Is it enough for the called routine to take a 'const &'? Basically what I want to tell the compiler is that the called function will not modify the object and to not make a copy of the object when passing it. Is there anything built into the C++ language that will guarantee this?


The '*' is actually the dereferencing operator for a pointer.

In C++, you want to use references in this sort of case. Passing by const reference is much the same as passing by value (normally) but still const. A reference, for all intents and purposes, is the original object. So, yes, const reference means that (excepting const_cast), the value will not be changed, and a copy will not be made of the object when passing it.

Share this post


Link to post
Share on other sites
Quote:
Original post by Todo
Whether or not const can speed things up, I personally find it good practice to favour consts above macros:
static const int THE_REASON = 42;

rather than
#define THE_REASON 42


Also, reading that (nice) linked document makes me think that it is one of those rare cases (const at birth) in which the compiler can toy with ROM. Correct?


You shouldn't need static there, const values have internal linkage by default AFAIK.

Share this post


Link to post
Share on other sites
Quote:
Original post by Todo
Whether or not const can speed things up, I personally find it good practice to favour consts above macros:
static const int THE_REASON = 42;

rather than
#define THE_REASON 42


Also, reading that (nice) linked document makes me think that it is one of those rare cases (const at birth) in which the compiler can toy with ROM. Correct?


Unless you're working in C.
const int LENGTH = 42;
int main(void) {
int array[LENGTH] = {0};
return 0;
}

This works in C++, is an error in C89, and I think it's a VLA in C99.

Share this post


Link to post
Share on other sites
Quote:
[...]So, yes, const reference means that (excepting const_cast), the value will not be changed, and a copy will not be made of the object when passing it.
Actually, you don't need const cast at all to change the value in a const reference, you just need aliasing:
void SomeFunc(const int &RefVal, int *const PtrVal)
{
//... use RefVal some ...
*PtrVal++;
//... use RefVal more ...
}
//elsewhere
int A = 5;
SomeFunc(A, &A);

Share this post


Link to post
Share on other sites
Compilers are generally smart enough to figure out whether or not you do actually write to variables, so it'll know where and for how long objects stay const.

The only example I can think of right now where 'const' might help optimise is something like interface functions. If you have an abstract class that defines a routine as 'const', the compiler knows no matter what object has actually inherited the interface, its external state wont change after calling. This might help it optimise, eg:

foo(myInterface->GetSomething());

myInterface->SomeConstMethod();

// Compiler knows state of 'myInterface' has not changed and hence
// can cache result of the first 'GetSomething', avoiding the cost of
// a virtual function call.
bar(myInterface->GetSomething());

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by Extrarius
Quote:
Original post by Deyja
[...]Const isn't an optimization.
except in rare cases.
Can you given an example of real compiler producing faster code with const than without?

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
Can you given an example of real compiler producing faster code with const than without?

I can't seem to find the link - but I recall a thread about a 3dVector::operator[] implementation that had some profiled stuff that was significantly faster if the offsets were const member pointers to floats as opposed to non-const versions.


// this is all from memory and isn't tested - so forgive any errors I may have
struct Vector3{

float &operator[]( std::ptrdiff_t idx ){ return offsets[idx]; }
float const &operator[]( std::ptrdiff_t idx ) const{ return offsets[idx]; }

float x, y, z;
// offsets to our x,y,z
static float Vector3::* const offsets[3];
};
float Vector3::* const offsets[3] = {
&Vector3::x,
&Vector3::y,
&Vector3::z,
};


In my opinion it is a moot point- as const-correctness isn't used for its optimization purposes - it is just a side-effect in some special situations.

Share this post


Link to post
Share on other sites
Quote:
Original post by Way Walker
Quote:
Original post by Nathan Baum
Quote:
Original post by Way Walker
<grasping at straws>In most cases I've come across (I don't speak for others) that's not a problem.</grasping at straws>

It may be that your compiler simply doesn't do the optimisation, so you would never have noticed even if you had thought to try consting your local variables.


I was actually saying that it's not common for my code to call functions with const arguments in loops.

I see. I const pretty much any argument which I can, which usually includes arguments to functions which would be called in loops. Consting arguments is getting so automatic for me that sometimes I have to modify a function because an argument was const when I actually wanted to modify it. It helps that the compiler can always detect when you didn't mean to use const, whereas it can't always detect when you did. Perhaps that's a point in favour of the ML family of languages, where variables are const unless explicitly declared mutable.

Share this post


Link to post
Share on other sites
Quote:
Original post by moeron
Quote:
Original post by Anonymous Poster
Can you given an example of real compiler producing faster code with const than without?

I can't seem to find the link - but I recall a thread about a 3dVector::operator[] implementation that had some profiled stuff that was significantly faster if the offsets were const member pointers to floats as opposed to non-const versions. [...]
That is exactly the one case I know of, and I also have never been able to find the thread with the example in it. Testing your code just now, the difference in output assembly is very minor, but there is more assembly when you remove the const, even though all my test code did was to initialize the vector using the named members and then print out the vector using the indexed version. I'm not saying it would actually be slower, because I don't know the assembly well enough to say, but it looked like the const version just directly output the numbers (it pushed them onto the stack as DWORDs then called STL functions) whereas the non-const version used mmx:
//Const Version
; 60 : Vector3 Test;
; 61 : Test.x = 0.12;
; 62 : Test.y = 1.34;
; 63 : Test.z = 2.56;
; 64 : cout << "(" << Test[0] << ", " << Test[1] << ", " << Test[2] << ")" << endl;

push OFFSET FLAT:??_C@_01PKGAHCOL@?$CJ?$AA@
push 1076090634 ; 4023d70aH
push OFFSET FLAT:??_C@_02KEGNLNML@?0?5?$AA@
push 1068205343 ; 3fab851fH
push OFFSET FLAT:??_C@_02KEGNLNML@?0?5?$AA@
push 1039516303 ; 3df5c28fH
push OFFSET FLAT:??_C@_01ODHLEDKK@?$CI?$AA@

//Non-Const Version
; 60 : Vector3 Test;
; 61 : Test.x = 0.12;

movss xmm0, DWORD PTR __real@3df5c28f

; 62 : Test.y = 1.34;
; 63 : Test.z = 2.56;
; 64 : cout << "(" << Test[0] << ", " << Test[1] << ", " << Test[2] << ")" << endl;

mov eax, DWORD PTR ?MemberOffsets@Vector3@@2PAPQ1@MA+8
mov edx, DWORD PTR ?MemberOffsets@Vector3@@2PAPQ1@MA+4
push esi
movss DWORD PTR _Test$[esp+16], xmm0
movss xmm0, DWORD PTR __real@3fab851f
push edi
movss DWORD PTR _Test$[esp+24], xmm0
movss xmm0, DWORD PTR __real@4023d70a
push OFFSET FLAT:??_C@_01PKGAHCOL@?$CJ?$AA@
movss DWORD PTR _Test$[esp+32], xmm0
mov ecx, DWORD PTR _Test$[esp+eax+24]
mov eax, DWORD PTR _Test$[esp+edx+24]
push ecx
mov ecx, DWORD PTR ?MemberOffsets@Vector3@@2PAPQ1@MA
mov edx, DWORD PTR _Test$[esp+ecx+28]
push OFFSET FLAT:??_C@_02KEGNLNML@?0?5?$AA@
push eax
push OFFSET FLAT:??_C@_02KEGNLNML@?0?5?$AA@
push edx
push OFFSET FLAT:??_C@_01ODHLEDKK@?$CI?$AA@
The next thing done in both versions was to push std::cout and call it's operator <<

Share this post


Link to post
Share on other sites
Quote:
Original post by moeron
In my opinion it is a moot point- as const-correctness isn't used for its optimization purposes - it is just a side-effect in some special situations.

As a general rule, the compiler can assume that any static const variable without mutable members is (1) never going to change and (2) already initialized whenever it is referenced.

Consequently, static const variables are literally constant and can always be optimized away into either an immediate (if the initializer can be evaluated at runtime) or a memory reference (otherwise).

e.g.


int x = 12;
const int y = 12;

int do_something ()
{
return x + y;
}


This compiles into "x + 12" even with optimisation turned off.


int naughty (const int & i)
{
int & j = const_cast<int &>(i);
return j++;
}

int value_of (const int & i)
{
return i;
}

int do_something ()
{
const int x = 42;
naughty(x);
return x + value_of(x);
}

int main ()
{
std::cout << do_something() << std::endl;
}


This example nicely shows the consequences of undefined behaviour when dealing with consts. This might reasonably output 84, 85, 86 or just crash (it might do anything, but those are the likely options). On GCC 4.1.1-r3 on x86 with optimisation disabled, it outputs 85: the direct reference to "x" is replaced at compile-time with 42, but value_of uses the actual variable which naughty() has modified.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this