
Why Const Doesn't Make C Code Faster - mnem
https://theartofmachinery.com/2019/08/12/c_const_isnt_for_performance.html
======
WalterBright
> This is true in practice because enough real-world C code has “I know what
> I’m doing” casting away of const.

I found this out the hard way when I implemented OptimumC, the first data flow
analysis optimizer for DOS C compilers back in the 1980's.

I had to undo doing optimizations on const.

Anecdote: Optimum C blew away all the other C compilers on a magazine
benchmark article because it figured out that the benchmark code did nothing
and deleted it. The author didn't ask me about it, he just assumed the
compiler was buggy and gave us a bad review. Sigh.

When other compilers did DFA, the benchmarks got changed.

~~~
asveikau
Just as in the saying about how the best code is the code you didn't write, I
guess the fastest benchmark is the one that doesn't run.

~~~
kkylin
Off-topic, but this discussion is reminding me of this (which some of you
might enjoy):
[https://www.cs.princeton.edu/~appel/papers/conteq.pdf](https://www.cs.princeton.edu/~appel/papers/conteq.pdf)

------
ljackman
The key difference between `const` and the `register` and `inline` keywords is
that, despite all of them often flagging optimisations that the compiler can
often work out anyway, `const` still aids human comprehension by declaring
constraints, whereas the latter two do not.

It seems that interprocedure optimisations in modern compilers make a lot of
const-by-reference optimisations apply even when the code is mutable-by-
reference in the parameter list but the function body doesn't modify it in
practice. This would only work if it could deterministically work out which
function is called.

Local constant stack values surely can be completely deterministically
verified as such by the optimiser even without the `const` modifier. They
could be overwritten without a C assignment to it, via the stack from a buffer
overflow of an array next to it, but that's undefined behaviour so a compiler
is presumably free to assume it is not modified, eliminate unnecessary
register/memory loading code, and let the developers deal with the
consequences.

As trailing `const` on member functions outlaws modifications via `this`, it
would follow that the same optimisation-even-without-modifier process would
apply as to `const` local stack values.

As constancy is a constraint that aids human comprehension, there's a good
reason for choosing a keyword just as short as the mutable equivalent, such as
Swift's `let` vs `var`; if the more constrained equivalent is equally or more
convenient, more constrained and thus easy-to-reason-about code becomes more
common.

~~~
Buge
While specifying inline alone is useless for optimization, the inline
specifier is important for another type of optimization: putting a function in
a header file. If you put a function in a header file and don't mark it
inline, that tends to be undefined behavior because it tends to violate the
One Definition Rule, (both C and C++).

~~~
daemin
Force inlining code is actually quite a useful optimisation still. We have
found many instances that after a few inline functions the compiler gives up
and just calls the function instead. Force inlining certain low level
functions has actually improved performance by several percent in our
codebase.

~~~
Buge
From the language standpoint, the inline specifier doesn't force the compiler
to inline the function. But I guess some compilers could provide guarantees
that go beyond what the spec says.

~~~
daemin
I was talking about __forceinline for MSVC and things like it for other
compilers. One's which for better or worse will make your function inlined.

One thing that I did find out what that even if you inline or force inline a
function, that if you have it exported from a DLL or in a class that is
exported as a DLL, then the function might still be called non-inline.

------
dmitrygr
gcc has some attributes to help with that, for example try
__attribute__((pure)) on funcs that will always return the same thing for the
same inputs (given the same global state) and do not modify said input or
state (and thus can be called more or fewer times than programmer wrote).
Usually gcc will be quite aggressive with optimizations if you use that.

For even more aggressiveness, try __attribute__((const)) which tells it that
the func accesses NOTHING but the params, not even global vars. Of course this
is quite limiting. You can also use this as a trick when _YOU_ know that the
only accessed global state is itself constant but gcc does not know that. This
can produce substantial savings in number of calls of funcs that, for example,
do lookups in large constant data tables, like table-based versions of sin()
and cos().

In the example the article gives, adding __attribute__((pure)) to constFunc()
does produce the behaviour author wanted and then some

LWN has more:
[https://lwn.net/Articles/285332/](https://lwn.net/Articles/285332/)

~~~
Ididntdothis
Does it enforce these attributes or are they just a promise by the code
author?

~~~
WalterBright
D has a `pure` attribute for functions, and yes it is enforced. It's enforced
even to the point where it becomes a PITA, but when you manage to make
functions pure, you know it's solid.

One pain point with pure functions is you can't insert debug logging
statements. D has a special case for that - purity for a statement isn't
checked if it is prefixed with the `debug` keyword:

    
    
        pure int square(int x) {
            debug printf("called square\n");
            return x * x;
        }
    

which is very, very handy.

~~~
einpoklum
Isn't that erroneous, in the sense that printf() has side-effects, breaking
the "pure" promise?

~~~
WalterBright
Indeed it does break the pure promise. But it's only for debugging builds.

------
BeeOnRope
Const does make C go faster, just not in most of the places you see it used.

For const to help, the _object itself_ needs to be _defined_ const. Just
taking a `const object __* ` (or `const object &` in C++) doesn't help you
determine the constness of the underlying object, and that usually accounts
for the majority of const usage by volume.

Limiting the scope to of actual const _definitions_ , it can help a lot, but
only in cases where the compiler couldn't move that anyways. So local variable
const definitions rarely help, because the compiler can often already prove
they are const by inspection (but they can help if the variable escapes).

They are useful especially for global variables (and moral equivalents, like
static class members in C++), since the compiler cannot prove by examining
only the current TU whether the variable is unmodified (and it is a hard
problem even if the whole program can be inspected), so const is a useful
promise there.

~~~
taylodl
BINGO!!! The pointer is constant, not the item being pointed to. Since you’re
dereferencing the pointer, i.e. accessing the non-const bits, the compiler
must reload.

~~~
bstamour
The examples in the articles are non-constant pointers to constant data. If
you want to declare the pointer itself const, you need to do it after the
asterisk.

    
    
        const int* const 
    

instead of

    
    
        const int*

~~~
oefrha
Exactly. const int* is a non-const pointer to a const int; int* const is a
const pointer to a non-const int. So I’m pretty confused by the claims.

Maybe the compiler will happily let you modify the dereferenced const int*
(undefined behavior), wouldn’t try it now, but that’s not what the signature
promises.

Edit: Thought about it more and read some other comments. Now it makes sense.

~~~
bstamour
If the initial object itself isn't const, then merely declaring the function
parameter as a pointer to const won't guarantee that some other thread of
execution won't change the value under your nose. Or if you have multiple
pointer parameters, they can alias each other.

Indirection in C and C++ is a mess, but at least C has the "restrict" keyword.
Best to program with value types whenever you can, and use pointers and
references when you must.

~~~
rrss
> won't guarantee that some other thread of execution won't change the value
> under your nose

My understanding is that compilers can always assume that there is no other
thread involved, which (part of) why C11 atomics are necessary. Is that not
the case?

~~~
alexlarsson
Yes, but it can't assume any random function call it does doesn't use a
different pointer to the same object which is not const. So, even if you don't
cast const to non-const pointers you can run into the const value changing.

~~~
bstamour
Concrete example:

    
    
      void foo(const int* const p1, int* const p2) {
        int a = *p1;
        *p2 += 42;
        int b = *p1;
        return a == b; // b = a + 42
      }
      int main() {
        int x = 0;
        return foo(&x, &x);
      }

------
mrpippy
On some microcontrollers, marking variables/arrays as const allows the
compiler to access them directly from flash rather than having to copy them
into RAM at startup. I used this to great effect on a PIC24 with 256KB
flash/16KB RAM.

~~~
jsd1982
Though this becomes incredibly annoying using const pointers when you didn't
mean to refer to ROM but instead meant a read-only pointer to RAM.

------
mlyle
This assumes that the compiler can actually infer that a const-by-reference
isn't modified.

The second we have indirection (function pointers), or a different source file
without whole-program-optimization, or a library, these assumptions break
down.

Further, the author assumes that the compiler can't benefit from the knowledge
that something is const because the const can be cached away. This isn't true,
e.g. per ISO9899 6.7.3.5:

 _If an attempt is made to modify an object defined with a const-qualified
type through use of an lvalue with non-const-qualified type, the behavior is
undefined. If an attempt is made to refer to an object defined with a
volatile-qualified type through use of an lvalue with non-volatile-qualified
type, the behavior is undefined._

------
klingonopera
I hardly ever bother optimizing my code anymore, with -O3 nothing I do ever
really seems to make a difference.

The real reason to use "const" is to show intent.

See "Const and Rigid Parameters" in this wonderful article about the Doom 3
source code: [https://kotaku.com/the-exceptional-beauty-of-
doom-3s-source-...](https://kotaku.com/the-exceptional-beauty-of-
doom-3s-source-code-5975610)

~~~
saagarjha
> I hardly ever bother optimizing my code anymore, with -O3 nothing I do ever
> really seems to make a difference.

Your choice of data structure or algorithm would…

~~~
klingonopera
Yes, true, though I consider that "structural/procedural" optimization as
opposed to "technical" optimization.

This means, I won't bother using "inline" anymore, but I will consider how
many subfunctions in total I'd call.

------
ridiculous_fish
const_cast risk is not why the compiler emits a reload. Indeed we don't even
need to pass x to constFunc to get the reload: see line 7 in
[https://godbolt.org/z/TjmWxL](https://godbolt.org/z/TjmWxL)

Consider that `x` may point to a global variable which `something` may modify.
That is why the reload is necessary.

edit: I just realized that the reload would be necessary even without an
intervening function call.
[https://godbolt.org/z/QhVzlV](https://godbolt.org/z/QhVzlV)

Consider that `x` may point to errno; then printf would modify it!

~~~
einpoklum
You're mistaken. What you're thinking of is a pointer to a _volatile_ ; for a
regular pointer, you only need one load - even if it's not a pointer-to-const.
See:

[https://godbolt.org/z/XWW9Nq](https://godbolt.org/z/XWW9Nq)

~~~
amalcon
I think you're talking past each other. Grandparent is examining the case
where there is a function call (the printf), but you do not pass anything
relevant into it. You're looking at the case where there is no function call,
so peephole optimization can observe that the memory at that location can't
change.

"volatile" basically tells the compiler "Hey, wait, this might be modified by
DMA, another thread, or another process. Assume nothing."

------
cogman10
I think the big problem here is that const doesn't really truly say anything
about aliasing.

Even excluding casting evils, calling a method with a const pointer only means
that the method isn't supposed to change the value. It does not mean that the
caller isn't going to change things (particularly from another thread).

The languages are simply too permissible when it comes to what a pointer means
and how it can be used.

For const to be optimizable, you'd need to take Rust's approach and make
language level guarantees that "You can't do bad things with this". C and C++
missed that boat.

~~~
mlyle
_Even excluding casting evils, calling a method with a const pointer only
means that the method isn 't supposed to change the value. It does not mean
that the caller isn't going to change things (particularly from another
thread)._

This is incorrect.

Actually, you promise not to change things from another thread, DMA, interrupt
handler, signal, etc, with _any_ non-volatile reference passed, let alone a
const! The compiler loads things into registers and has no way to know if
memory in a passed reference changes underneath the hood-- it freely generates
code that assumes that the things it has pointers do, do not change. It can
freely make optimizations that lead to incorrect computation, infinite loops,
segmentation faults if this is not obeyed. If you've ever head about how
"double check locking" is an antipattern, this is a big part of why.

e.g. from ISO 9899:

 _Alternatively, an implementation might perform various optimizations within
each translation unit, such that the actual semantics would agree with the
abstract semantics only when making function calls across translation unit
boundaries. In such an implementation, at the time of each function entry and
function return where the calling function and the called function are in
different translation units, the values of all externally linked objects and
of all objects accessible via pointers therein would agree with the abstract
semantics. Furthermore, at the time of each such function entry the values of
the parameters of the called function and of all objects accessible via
pointers therein would agree with the abstract semantics. In this type of
implementation, objects referred to by interrupt service routines activated by
the signal function would require explicit specification of volatile storage,
as well as other implementation-defined restrictions._

This is the model used by pretty much every C compiler you'll encounter. When
you e.g. acquire a lock, you call something in a different linkage unit so
multithreaded stuff behaves properly.

For const, the guarantees go further: you promise it won't change elsewhere
(relevant standards text quoted in my other comment).

~~~
jcranmer
Your comment is incorrect as well.

The C specification describes semantics in terms of an abstract machine which
is actually quite different from real hardware (most notably in terms of how
memory works!). It then goes on to say that the compiler may choose to
implement it radically differently, so long as the observable semantics (I/O
calls and volatile accesses) are preserved. I'd have to double check whether
it was C or C++ that said that the compiler is free to assume that infinite
loops do not exist.

> Actually, you promise not to change things from another thread, DMA,
> interrupt handler, signal, etc, with any non-volatile reference passed, let
> alone a const!

This is not the case. The C specification requires that you use volatile to
indicate that code outside of the C execution model may access the memory
location. Of your list, only DMA and interrupt handlers are outside the
execution model; signal handlers and threads are both considered inside the
memory model. The only way for a signal handler to communicate with code
outside the signal handler is with volatile sig_atomic_t; volatile int does
not cut it. To communicate between threads, you need to ensure proper
synchronization. This may involve the use of locks, fences, or atomics with
appropriate orderings chosen.

> The compiler loads things into registers and has no way to know if memory in
> a passed reference changes underneath the hood

To be pedantic, the compiler relies on undefined behavior here. It is
undefined behavior if you cause the value to be changed in a way that violates
these rules, so the compiler has absolutely no restrictions on what may happen
in such executions.

> If you've ever head about how "double check locking" is an antipattern, this
> is a big part of why.

This has _absolutely_ _nothing_ to do with why double-checked locking is
incorrect. Double-checked locking is problematic in large part because of
_hardware_ reordering of loads and stores. In general, you need a store
barrier to guarantee that all of the modifications the first thread changed
has been made visible to other processors followed by a load barrier to
guarantee that all prior modifications from other processors have been made
visible to the second thread. Volatile does absolutely nothing to provide
these barriers (except if you use MSVC, which documents that they treat
volatile variables as equivalent to acquire/release semantics on x86 because
regular loads and stores on x86 have those semantics anyways--this is
nonportable behavior). The double-checked locking pattern does not provide a
load barrier in the second thread, which means the ordering semantics are not
guaranteed. If you use atomic loads and stores when implementing double-
checked locking, you do get the necessary semantics for correctness.

~~~
mlyle
??

You say that it is only thing outside of the execution model that require
volatile, but you explain that a signal handler requires volatile _and_ to be
the signal-atomic type. ;) In practice, volatile with lock-free types (that
now stdatomic has a way to query whether types are lock-free) are correctly
used all the time to pass data between threads.

 _A volatile declaration may be used to describe an object corresponding to
a... an object accessed by an asynchronously interrupting function._

Like, execution in other thread contexts.

Or, from the C99 Rationale:

 _The translator may assume, for an unqualified lvalue, that it may read or
write the referenced object, that the value of this object_ _cannot be changed
except by explicitly programmed actions in the current thread of control_ _,
but that other lvalue expressions could reference the same object._

vs volatile:

 _No cacheing through this lvalue: each operation in the abstract semantics
must be performed (that is, no cacheing assumptions may be made, since the
location is not guaranteed to contain any previous value). In the absence of
this qualifier, the contents of the designated location may be assumed to be
unchanged except for possible aliasing._

> Double-checked locking is problematic in large part because of hardware
> reordering of loads and stores.

This was unsafe even on in-order uniprocessors, because the compiler was free
to reorder loads and non-aliased stores.

~~~
jcranmer
> You say that it is only thing outside of the execution model that require
> volatile

No, I'm saying that the statement that volatile is only necessary and
sufficient for outside the execution model. For signal handlers, it is
necessary but not sufficient; for threads, it is neither necessary nor
sufficient.

> Or, from the C99 Rationale:

C99 does not consider threading at all. C11 and C++11 do, and any compiler
written in the past decade is going to be obeying the rules for the C++11
memory model (which C11 adopted as its memory model). The committees
explicitly considered whether it would make sense to imbue volatile with any
special threading semantics, and they explicitly rejected doing so.

A volatile read or write is _not_ guaranteed to be converted into a single
hardware load or store, even for lock-free types. There are situations where
the compiler will narrow or widen the load/store, or even insert extraneous
loads and stores to the value.

> This was unsafe even on in-order uniprocessors, because the compiler was
> free to reorder loads and non-aliased stores.

The compiler is free to reorder non-volatile loads and stores with volatile
loads and stores. Only reordering volatile loads and stores with respect to
other volatile loads and stores is prohibited. Volatile is not sufficient to
make double-checked locking safe.

~~~
mlyle
I've been quoting C99 and my answers should be considered in that context.
However, to say C99 does not "consider threading at all" is to neglect that
the standards body document above explicitly mentions the thread of
execution... in the quote that I stated.

You seem to not be reading what I'm saying and also to be arguing with things
I never asserted. e.g.

> Volatile is not sufficient to make double-checked locking safe.

OK, but I didn't say volatile makes double-checked locking safe... I said that
the compiler's ability to reorder non-qualified variable accesses is "a big
part of why" double-checked locking is an antipattern. My statement:

> Actually, you promise not to change things from another thread..., let alone
> a const! The compiler loads things into registers and has no way to know if
> memory in a passed reference changes underneath the hood... It can freely
> make optimizations that lead to incorrect computation, infinite loops,
> segmentation faults if this is not obeyed. If you've ever head about how
> "double check locking" is an antipattern, this is a big part of why.

------
WalterBright
This issue is why D has both const and immutable qualifiers. const can't be
optimized because there may be other mutable references to the same memory
object. But for immutable references, there cannot be.

It is possible to cast away const and immutable in D, but these are only
allowed in system code, presumably where the programmer actually does know
what he's doing.

------
pcwalton
The most succinct way I've heard this explained is: "Const means ' _I_ won't
mutate this'. It doesn't mean ' _nobody_ will mutate this'."

To do optimizations, compilers really need to know that _nobody_ will mutate a
value; just knowing that a particular function won't mutate a value isn't that
helpful.

~~~
missblit
True to form, there's multiple forms of const in C++.

const on a methond says "I won't mutate the state of this class (except for
members explicitly marked as mutable)

A const reference says I won't mutate this (unless I const_cast away the
constness first)

But a const variable actually _is_ a promise to C++, saying "This object will
never mutate after initialization ever forever I promise for real this time _"
(_except in the destructor, then it's OK).

Confusingly, all together this means you're only allowed to cast away
constness if the object wasn't const to start with.

___

Some of the above may be wrong, not a language expert.

~~~
nikbackm
You are allowed to cast away const-ness of an actual const object, just as
long as you don't modify it.

------
not2b
Unfortunately in C++, the compiler is not allowed to assume that if it passes
a const reference to a function, that function will not change the object. The
function is allowed to cast away const and modify the object. I'm not happy
that they did it that way; I would have preferred it if cast-away-const were
more restricted (for example, allowed when calling a child function that takes
a char* but doesn't modify the pointed-to C string), with the idea being that
if a function has only a pointer to const or a reference for a const object,
it has read permission on the object and lacks write permission. But that
isn't how the language works.

~~~
saagarjha
The fact that std::launder
([https://en.cppreference.com/w/cpp/utility/launder](https://en.cppreference.com/w/cpp/utility/launder))
exists blows my mind. Like, why is this a thing that the standard allows?

~~~
jcranmer
[http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2017/p053...](http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2017/p0532r0.pdf) has an explanation.

In short, placement new (or some scenarios involving unions) technically cause
undefined behavior if you try to use the object after the call to placement
new, since the pre-existing object there has had its lifetime expire.
std::launder lets you use a pre-existing pointer to the memory at the same
location to access the data there.

~~~
saagarjha
Yes, I know what std::launder does, and that document contains some of my own
thoughts: namely, placement new (the only use I've seen suggested) should
automatically launder, and std::launder should just not exist.

~~~
gpderetta
AFAIK placement new does launder. But both new and launder have no effect on
their argument (well, placement new of course constructs an object there),
they only 'bless' pointer returned from it. The use case is if you placement
new on some byte storage. Now you want to access the object stored there, and
of course you do not want to placement new the storage again, nor you have
cached the result of the previous placement new (it would be suboptimal),
instead you want to get a pointer to T from the storage address itself.

~~~
saagarjha
> AFAIK placement new does launder. But both new and launder have no effect on
> their argument (well, placement new of course constructs an object there),
> they only 'bless' pointer returned from it.

I may be misunderstanding, but this seems to directly contradict what the
linked paper says:

> Note that std::launder() does not “white wash” the pointer for any further
> usage.

> The obvious question is, why don’t we simply fix the current memory model so
> that using data where placement new was called for implicitly always does
> launder?

~~~
gpderetta
> I may be misunderstanding, but this seems to directly contradict what the
> linked paper says: >> Note that std::launder() does not “white wash” the
> pointer for any further usage.

no, it is consistent. Launder does not white wash its parameter, only its
return pointer and any pointer returned by it. Same for placement new.

------
Ididntdothis
I have done a lot of profiling work and I have observed similar things. One
thing is const, another is virtual functions. A lot of people think they add
overhead and avoid them for performance reasons but my profiling almost never
showed them as a problem. Same for const and inline. it’s really hard to
predict what the optimizer will do.

Obviously there are stupid things that can be avoided from the start but in
general I prefer clean code where people write for readability and simplicity
and not speed.

~~~
dbcurtis
Inlining especially is call-site dependent. Little of the benefit of inlining
comes from eliminating the call overhead on a modern processor. But quite
often, functions are called with one or more parameters constant. So that
enables a bunch of constant folding for that call site -- the compiler is good
at noticing that if(1<7) is always going to be true, and dropping out the
whole else branch, which can enable code motion and more common
subexpressions... so inlining is often a win but often people misunderstand
exactly why.

~~~
rrss
"Inlining is a gateway drug^H^H^H^H optimization"

------
QuadrupleA
I used to strive for "const correctness" in my game engine code, and wasted a
fair amount of time fighting the compiler and editing function signatures as
requirements changed. Mostly eliminating const from the codebase simplified
things a lot.

Didn't do any comparison studies of performance, but the productivity boost
from not having to think about it was nice, and I suspect any performance
difference is margin-of-error stuff like the sqlite results in this article.

~~~
account42
Productivity boost from not writing tests or thinking about function
preconditions is also nice ... until it isn't.

------
mcv
Not quite the same issue, but this reminds me of my very first job, back in
2001, where we used a homebrew C++ framework that separated lots of concerns,
and then used a single massive .h file with 10,000 const uints to tie
everything together.

Compiling took over 2 hours. The header was generated automatically, but after
I added a macro that replaced those const uints with #defines, compile time
dropped to 30 minutes, which was quickly voted the biggest productivity
enhancement in that project.

More relevant to the topic, in javascript programming I recently switched to
using const instead of let wherever possible. Most variables don't actually
vary, so let's make that explicit. That can prevent some unexpected surprises
(though not all, as objects and arrays in constants are not immutable).

------
chmaynard
Many laws exist that express the intent of a law-making body but are either
unenforceable or too expensive to enforce in any meaningful way. Perhaps the
const declaration in C is an example of this type of law.

In contrast, a Standard ML compiler can enforce this law because the language
itself insists that all bindings are immutable.

------
jasonzemos
I wish there was a C++ compiler flag which made everything const by default
and required the mutable keyword otherwise. This is obviously non-standard and
would break included headers, but alternatively a #pragma could scope that
option for project code coming after non-project includes.

------
kazinator
Well, of course:

    
    
      void constByArg(const int *x)
      {
        printf("%d\n", *x);
        constFunc(x);
        printf("%d\n", *x);
      }
    

Here, the object referenced by pointer _x_ is non-local, and so is
_constFunc_.

The object could be modified in legal ways nothing to do with _constFunc_
stripping away the qualifier.

Also, if the object is not _defined_ const, then _constFunc_ is allowed to do
that, too.

Then the next, correct, example with a const protected local shows that two
instructions are shaved off. That's a wortwhile saving that could be leveraged
to get faster code.

If you're passing local variables into helper functions which are not supposed
to change them, you can shave off some cycles with const.

> _I mean, I removed const from the entire program_

Maybe sqLite doesn't use _const_ specifically with a view toward optimization.
To see an overall performance impact, there would have to be some case in a
"hot spot" of the program, where _const_ is used with some local variables
being passed into functions. (Or whatever other case we can ferret out where
_const_ happens to help.)

There are some widely applicable optimizations which scoop up all the "low
hanging fruit" improvements, but after that, optimization is a game of eking
out small gains with specialized cases.

------
ndesaulniers
If clang can see the definition of `constFunc` and deduce that its parameters
are `noescape`, then I think it can avoid reloading `x`.

A recent optimization in clang (not sure if it's in clang-9 or clang-10) will
remove memsets to variables declared const, which usually come from assigning
through a pointer that's had const (of the pointed to type) casted away. The
MIPS Linux kernel won't boot when built with clang due to the above (I sent a
patch 2 weeks ago)

------
foxhill
overlooks the value of const qualification for the caller - if the argument is
marked as const, the caller might reasonably assume the data won’t be changed.

~~~
vortico
Yes, there are many cases where the caller can optimize the dependency chain /
order of

    
    
        constFoo(x);
        constBar(x);
    

where it couldn't make that assumption if one function wasn't const.

~~~
mlyle
Unfortunately, C++ const functions are not necessarily pure functions and
cannot be freely reordered.

~~~
vortico
You're right, I was mistaken. And if the function is in the same compilation
unit so the compiler is able to prove that it's pure, it would also be able to
prove that a non-const pointer is actually const.

------
sclangdon
C++, rather than C, but there was a GOTW post about this years ago, which
explains the situation in detail.

[http://www.gotw.ca/gotw/081.htm](http://www.gotw.ca/gotw/081.htm)

------
caf
It's true that, as demonstrated, const-qualifying your pointer arguments is
very unlikely to allow any optimisations on its own.

However, const-qualifying your pointer arguments where the pointed-to object
isn't changed is what allows you to make more liberal use of const-qualified
_declarations_ , and as also demonstrated by the article, those in turn do
allow some optimisations.

Additionally, there is some safety on the table: if your C program makes use
of pointers to structures full of function pointers to implement polymorphism,
making those pointers const-qualified allows your underlying structures full
of function pointers to be declared const, which in turn allows them to be
stored in hardware-enforced read-only memory.

As a side note, I've often thought that block-scope variables declared const
and whose address is never taken should be automatically made static.

------
Someone

      // x is just a read-only pointer to something that may or may not be a constant
      void constFunc(const int *x)
    

Is that correct? I read that as “x is a pointer to a const int”

If you want “x is a read-only pointer to an int”, you would need

    
    
      void constFunc(int * const x)
    

The article also doesn’t mention the case “x is a read-only pointer to a
constant int”. To state that, you would use

    
    
      void constFunc(const int * const x)
    

([https://stackoverflow.com/a/1143272](https://stackoverflow.com/a/1143272))

~~~
mikeash
I believe the distinction they’re making is that there is actually no
guarantee that the value pointed to by x will remain constant. All this tells
you is that you’re not allowed to use x to make the modification.

Consider:

    
    
      int foo = 42;
      const int *x = &foo;
      foo = 43;

~~~
dmitrygr
Restrict keyword does do that if you wish to say that to the compiler. It
tells it that no parameters point to the same memory. So if you won't change
things via x, *x won't change.

------
rurban
Overly broad statement. const for global or static variables do make code
faster, as they are placed in the .rodata segment, which allows swapping them
out for free. This can be a huge win.

He only talks about const args, where the compiler does not try yet to check
for casts which violate the constness guarantees. (DFA, Escape Analysis). And
thus misses all important optimizations. You cannot rely on that never being
implemented in the future. With LTO they already do I think.

------
fallingfrog
I use const all over the place, but no so much to make the code faster as to
eliminate potential stupid mistakes.

------
gavanwoolery
Const won't necessarily make things faster but constant values will (whether
or not explicitly inferred). I noticed, for example, a 2x+ speedup in one of
my ray tracers by downgrading my dynamic 2d/3d/4d vector class* to always have
a constant size of 3.

* (poor choice, I know)

~~~
sdinsn
Having a constant size probably made it easy for the compiler to properly
generate bytecode that uses vectorized instructions

~~~
BubRoss
I would say that's unlikely. SIMD instructions don't usually get used by
normal compilers without very specific loops that make sure there are no
obstacles to vectorization. Also the best way to use SIMD is to loop through a
large array of two and do very simple operations with them. Modern CPUs
actually have three (I think) floating point slots so their total floating
point throughput isn't simply a fraction of the SIMD size.

~~~
sdinsn
> SIMD instructions don't usually get used by normal compilers without very
> specific loops

Compilers are smart. Even Java's JIT will generate SIMD instructions pretty
well

~~~
BubRoss
I would have to see this to believe it. Even Intel's own compiler is extremely
sensitive to small changes turning off SIMD use in a loop. You can try out
compilers and see their asm at godbolt.org

------
tutfbhuf
You should also test it with older compiler versions from the 80s/90s. There
are some falsehood believes about programming now that were true some decades
ago. I'm not saying that this is the case here, but it's worth to keep that in
mind.

------
numlock86
I just see too many programmers that right from the start try to micro-
optimize their code with things like this. You'll usually see very well
written code but with all sorts of hacks, taken from blogs and StackOverflow
answers and whatnot all across the web, but in the end they fail to optimize
their actual algorithm and end up with some ultra-high memory footprints or
O(n^3) stuff instead of some nice O(log(n)) for example. And they'll argue
that they already "optimized the sh*t out of it", because they saved a couple
of hundred assembly calls in a binary file around the size of a few megabytes
...

------
SomaticPirate
Is the same true for other compiled languages? Like Golang?

~~~
kzrdude
Rust has shared references where you can trust the pointer is actually
immutable.

------
haberman
> So, what’s const for? For all its flaws, C/C++ const is still useful for
> type safety.

Const variables can also be mapped read-only. This gives you hardware
protection against modifications, and also uses less RAM if the const variable
is in a shared library (multiple processes can share the same mappings).

------
3xblah
Surprised no one has mentioned what the author of C, Dennis Ritchie, said
about the addition of "const" to the language.

[https://www.lysator.liu.se/c/dmr-on-
noalias.html](https://www.lysator.liu.se/c/dmr-on-noalias.html)

~~~
favorited
> Assigning an ordinary pointer to a pointer to a `noalias' object is a
> license for the compiler to undertake aggressive optimizations that are
> completely legal by the committee's rules, but make hash of apparently safe
> programs

I can only imagine what he thought about aggressively optimizing C-family
compilers in his later years.

------
craftoman
We're talking about billions of calls that have milliseconds in difference.
That's irrelevant when building a basic app that sits on a 10K worth of
hardware machine with hundreds of CPUs.

------
kmadento
Shouldn't the test instead be: const int * const <variable name>

Haven't profiled it but would make more sense to have the pointer also be
const instead of having a non const pointer as input?

~~~
jforberg
It rarely makes sense. Pointer to const is a contact between caller and
callee. A signature like char <star>strdup(const char <star>) says "I take a
pointer to memory that I promise not to modify, and you get a pointer to
memory that you may modify".

Const pointer is a statement about the internal variables of a function
definition, usually not of any interest outside the function itself and
therefore rarely used.

~~~
MauranKilom
> Const pointer is a statement about the internal variables of a function
> definition, usually not of any interest outside the function itself and
> therefore rarely used.

...and in fact not even part of the name mangling (for the exact reason you
mentioned): [https://godbolt.org/z/1pjecq](https://godbolt.org/z/1pjecq)

------
badsectoracula
> So most of the time the compiler sees const, it has to assume that someone,
> somewhere could cast it away, which means the compiler can’t use it for
> optimisation. This is true in practice because enough real-world C code has
> “I know what I’m doing” casting away of const.

Some years ago i'd agree, but experience has shown that C/C++ compiler writers
would rather win benchmark games than keep working code working. So it'd be
nice if there was a better reason than "well, a lot of code would break if
they did that".

~~~
mafuyu
That's typically in the case of UB, where compilers can make whatever
assumptions they want. In this case, even if the code is completely within the
C spec, the compiler cannot guarantee that the data is unmodified.

------
ma2rten
It seems to me that compilers could benefit from doing analysis of functions
and providing that information to callers.

~~~
gpderetta
they do.

------
known
Extra protection of data costs speed;

------
stephc_int13
I once had a very stubborn intern who decided to add as much const as he could
in our codebase. What followed was a heated discussion and I did a global
replace of const by nothing.

The main problem with const is that it's ugly, it's viral and it does not add
any value to the code.

And I know all the supposedly good things about this, but I never found any
real usage in practice.

~~~
will4274
You should fire yourself. Seriously.

Understanding which object parameters to a function are inputs and which ones
are modified is key information about the behavior of a function. It's
omission requires devs to divine the intent from the name and hope that every
previous dev was a good citizen w.r.t keeping function names accurate to
intent.

Finding and replace const with empty is the moral equivalent of replacing all
the types of parameters with void*. After all, types are viral and ugly too.

I really hope that intern found a better place to work than your company.

~~~
icedchai
With "const" you still need to rely on devs being good citizens since const
can be trivially casted away.

I've been programming in C for 25+ years. const is clutter.

~~~
marcinjachymiak
Don't let consts get "trivially" casted away in code review. If something is
no longer const than remove the keyword, or find another way to solve your
problem

