Submitted via IRC for SoyCow3196
Why const Doesn't Make C Code Faster
In a post a few months back I said it's a popular myth that const is helpful for enabling compiler optimisations in C and C++. I figured I should explain that one, especially because I used to believe it was obviously true, myself. I'll start off with some theory and artificial examples, then I'll do some experiments and benchmarks on a real codebase: Sqlite.
Let's start with what I used to think was the simplest and most obvious example of how const can make C code faster. First, let's say we have these two function declarations:
void func(int *x);
void constFunc(const int *x);And suppose we have these two versions of some code:
void byArg(int *x)
{
printf("%d\n", *x);
func(x);
printf("%d\n", *x);
}void constByArg(const int *x)
{
printf("%d\n", *x);
constFunc(x);
printf("%d\n", *x);
}To do the printf(), the CPU has to fetch the value of *x from RAM through the pointer. Obviously, constByArg() can be made slightly faster because the compiler knows that *x is constant, so there's no need to load its value a second time after constFunc() does its thing. It's just printing the same thing. Right? Let's see the assembly code generated by GCC with optimisations cranked up:
(Score: -1, Troll) by Anonymous Coward on Sunday August 25 2019, @02:20PM (8 children)
They could try using ProggyClean with blackboard theme for readability. Code-wise, they could use Contributor Covenant to squash any inclusivity bugs.
(Score: 0) by Anonymous Coward on Sunday August 25 2019, @02:46PM (4 children)
Here we go, was not enough to watch for null pointers and bounds but now also for minorities and LGTB code.
What's next, an FSM exception code?
(Score: 3, Funny) by The Mighty Buzzard on Sunday August 25 2019, @02:56PM
Nope, exceptions are problematic since they imply that the caller is somehow exceptional.
My rights don't end where your fear begins.
(Score: 0) by Anonymous Coward on Sunday August 25 2019, @07:28PM (1 child)
What should I do if my NullPointerException has completed hir transition from an ArrayOutOfBoundsException?
(Score: 2) by maxwell demon on Sunday August 25 2019, @08:55PM
Throw a SnowFlakeException.
The Tao of math: The numbers you can count are not the real numbers.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @03:03PM
More like the owners and managers who recognize harassment and gay bashing don't hire people who create hostile work environments, and intelligent open source projects don't give a shit about your mad skillz until you learn to be a human being who doesn't hold people's sexual orientations or physiologic characteristics against them.
I suppose you're against working with all them cripples and darkies too.
(Score: 0) by Anonymous Coward on Sunday August 25 2019, @02:57PM (2 children)
Please don't let this guy on our team. Please don't let this guy on our team. Please don't let...
(Score: 0) by Anonymous Coward on Sunday August 25 2019, @06:48PM (1 child)
You are just scared the boss will find out your code is riddled with exclusivity.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @10:50AM
Yes, it's scary. She may get promoted and asked to supervise snowflake's blackbox gay code, and that sucks.
(Score: 5, Insightful) by Dr Spin on Sunday August 25 2019, @03:02PM (3 children)
... by using const - with two printf()s in there?
WTF?
You could save two whole CPU cycles out of about 5k cycles?
Besides which, if the value does not change, then the optimiser should create the same code whether const is there or not.
What machine architecture are you using, with what caching? Have you looked at the code this generates? Could you
read it anyway?
Compiler writers are mostly very clever* people, and most C compilers have had more than 10 years of optimisation.
* probably also autistic, OCD and fanatic about saving 1/2 a CPU cycle in even the most obscure cases, and definitely
out to prove they are better than the last guy.
Warning: Opening your mouth may invalidate your brain!
(Score: 0, Touché) by Anonymous Coward on Sunday August 25 2019, @09:08PM (2 children)
saving 2 cycles out of 5k cycles, for a single pass, your right why waste the time.
Now call it 2G times... That is real processing power waste.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @06:34AM (1 child)
By your logic putting two lines of code in a function and calling the function 2k times is the real rea processing power waste. Calling functions is expenssive.
(Score: 2) by Pino P on Monday August 26 2019, @03:36PM
Finding opportunities to expand a function inline as if it were a macro is why compiler developers invented link-time optimization (LTO). SQLite uses a related technique called "amalgamation," where the entire library is compiled as a single translation unit.
(Score: 2, Interesting) by isj on Sunday August 25 2019, @03:10PM (9 children)
I assume that many C/C++ programmers already knew that const doesn't necessarily improve performance. But it's still good that there is an article about it so younglings can learn - we old farts are not always that good at disseminating knowledge like that.
I don't like that the article's first proof is the generated assembly, but I guess that quoting chapter and verse(s) from the C standard would have made most readers eyes glaze over. I think it would have been better to first show in source code why the compiler cannot assume that the pointed-to contents of a const pointer cannot change, then quote chapter and verse from the standard, then finally prove it with generated assembly.
(Score: 5, Informative) by Ethanol-fueled on Sunday August 25 2019, @03:32PM (4 children)
The point of const is to make variables immutable so people don't fuck with them, not optimize for speed.
(Score: 2, Informative) by Anonymous Coward on Sunday August 25 2019, @04:44PM (1 child)
I was waiting for someone to point this out.
Furthermore at least back in the DOS, proprietary unix and 8bit days, I seem to remember it providing other benefits that could speed things up, but it was a secondary effect mostly due to the optimization paths compilers of the day used.
const's primary purpose has always been to ensure a pointer is immutable within the context of the C code itself. Writing in assembly could always bypass it and certain C compilers would fail to take it into account in all circumstances if you were particularly 'creative' with your means of access or rewriting of the pointer. But if you were following the standards for data typing, const was there to protect your functions or data structure from someone doing something stupid or malicious while working with a pointer or data structure.
We used to call them clobbering vs non-clobbering functions which either modified in place, or had to create and return a new copy of a data structure with a new pointer in order to ensure consistency in the codebase. It was the tradeoff between memory consumption and ensuring a sane program state in the older memory constrained days of application and operating system development.
(Score: 2, Informative) by Anonymous Coward on Sunday August 25 2019, @06:49PM
Look at his command line.
That is the most aggressive optimization switch. Two of those are basic constant folding and basic constant propagation. When compiling, it can look and determine both of the variables, despite only one being marked, are not changed during actual execution and makes such a substitution, when it believes it is safe to do so. To your point, when GCC compilers were dumber "const" was important because it would signal the compiler you could handle the constants that way and it didn't have to guess whether or not it was safe.
And that doesn't get into any other black magic the -O3 switch will do. If anything, he should have shown what a bare command line, -O, -O2, and -Os, all against the same code. He is smart enough to know the difference between a t test and a U test (although not that these are dependent samples and a different test should be used). I reckon he is smart enough to try and show the difference that way.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @12:10AM
const doesn't necessarily make objects immutable though. The variable can be changed elsewhere. const instead specifies access permissions to a variable.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @04:24PM
Yeah but that's why God had to invent const_cast to override the excessive use of const by others to protect us.
(Score: 2, Interesting) by Anonymous Coward on Sunday August 25 2019, @09:20PM (3 children)
I once spent several weeks arguing with one of my fellow senior engineer's over this exact thing.
I *SHOWED* him how it was not doing anything. He had spent 2 weeks putting 'const' all over the code. Before functions in the api, in params, ... everywhere.
He was one of those show off programmers that 'knew better' than everyone else. Would just nod smugly when I showed him how very very very wrong he was. Then ignore the advice of anyone on the team then yell at them because they did not follow his style guide which was only in his head. Ignoring the one the team had already agreed to and was doc'd in the wiki.
Fucker you wasted a whole sprint fucking around with this?! The API did not need to be changed because it did not match the style guide from your former company. In fact it did not match the current company you worked at style guide.
If you can not tell me what it is doing to your code get the fuck rid of it. I am reverting your whole sprints work because it adds no new features and only makes using variables harder for little gain. You wasted 5 hours of my time because I thought you were competent. Not anymore as now I have to extra scrutinize what you are doing and have to instruct others to do the same.
Considering how many F bombs I am dropping you may think I was upset about this. Yes. Yes I am. I am usually fairly easy going with most changes. Because they are usually just fine. But this one was just too much.
Constexpr in C++ on the other had has *tons* of uses. But put a const in their alongside it? Well you can just kiss your pre-compiled optimizations goodbye as const is a value decided at runtime thing. More along this thing is set here in code once and never modified again. While constexpr you can figure it out at compile time.
Const is for things like where you would do something like #define XYZ 2. That has its uses and a nice way to enforce type. But even then not always needed.
Not for every parameter and every function you can lay your hands on.
Now do not get me wrong const has its uses. It is a modifier that should be used when needed, not dictated by style.
What riles me up is the style nazis have glommed ont this this as something that is 'better' with no real reasons as to what the expression actually does. This is not so much a big deal anymore but it even varies between compiler sets what it actually does. The spec says what it does not what the compiler should with the optimizer in this case other than a delineation point of what can be optimized. That portion is usually an assumption by the programmer. Usually it is wrong.
(Score: 5, Informative) by Immerman on Monday August 26 2019, @02:08PM (2 children)
I disagree. Const is a valuable modifier that should be applied to any function interface where you're accepting parameters by reference, unless you explicitly intend to modify those parameters.
Not because it makes code faster. Not even because it guarantees that the parameters won't be changed (it's almost always a bad idea, but you can always cast away const). But because it establishes the contract between function and calling context that the parameters will not be changed. How else are you going to do that? A sentence in the function comments that will almost never be read, and won't invoke any compiler errors to warn you when the contract is broken?
Not to mention the fact that if your function takes non-const parameters that it never intends to modify, then you've just made it impossible to call with const data. A simple example - you declare a function:
int CountLetters(char* theString);
And you can't do something simple like call that function as
int c = CountLetters("Some String #$ with 7298 letters, spaces, punctuations, etc");
Put that in a larger context where you might want to call that function from within another function that *does* formally establish the contract and... you can't. Not without casting away const on the big fugly data object your own function asserted it would not modify. When a function fails to assert the constness of its unmodified by-ref parameters, it creates headaches for everyone upstream - forcing them to either leave their own constness unstated, or engage in const-casting.
>Const is for things like where you would do something like #define XYZ 2....
It is also handy for declaring constants in that manner, but that's hardly it's primary use
(Score: 0) by Anonymous Coward on Monday August 26 2019, @04:28PM (1 child)
Defining arguments as const is like soldering the RAM on the motherboard. 640k enough for everybody. Pats self on back.
(Score: 2) by Immerman on Monday August 26 2019, @04:48PM
How exactly is promising not to modify the string you asked me to analyze even remotely similar to hard(ware)-limiting resource constraints?
You expect that a function that has no need to modify its parameters is going to suddenly reach a point where it needs to do so? And more to the point, that you could change it to start modifying its parameters without causing all sorts of unexpected problems in the many places it was called in contexts that assumed it wouldn't?
(Score: 4, Insightful) by Anonymous Coward on Sunday August 25 2019, @04:03PM
Pointer constness is not about illusion of speed, it is about illusion of safety.
(Score: 0) by Anonymous Coward on Sunday August 25 2019, @04:15PM
Const makes C faster like speed holes make your car faster.
(Score: 3, Interesting) by SemperOSS on Sunday August 25 2019, @04:48PM (8 children)
I rarely ever use const in my programs, except occasionally as a reminder to myself.
I recently did some one-on-one C coaching/mentoring of a young man, who had just started university and told him the rationale behind keywords like const and register, the latter being entirely anachronistic these days, really. I am not entirely sure he fully grasped the ingenuity of early computer scientists and programmers, who managed feats like writing compilers for languages such as ALGOL and FORTRAN on a computer with 16kB of core memory.
I don't need a signature to draw attention to myself.
Maybe I should add a sarcasm warning now and again?
(Score: 1, Funny) by Anonymous Coward on Sunday August 25 2019, @05:07PM (3 children)
C, Algol, and Fortran are obsolete languages for old people. Rockstar coders don't use old dead languages for social media marketing. Coding today is about selling an app to a billion idiot users, not about being clever with a computer.
(Score: 2) by barbara hudson on Sunday August 25 2019, @07:17PM (2 children)
Speaking of which, WTF is up with getting rid of the "volatile" keyword?
SoylentNews is social media. Says so right in the slogan. Soylentnews is people, not tech.
(Score: 2) by Immerman on Monday August 26 2019, @04:56PM (1 child)
Say what?!? They're getting rid of volatile? I hadn't heard that, and it's madness. Not that I've used it very often, maybe once every few years, but when it's needed....
What's the alternative? Especially in a world where multi-threading has become compulsory if you want decent performance. Just assume everything is volatile and eliminate vast swaths of optimizations? Lock anything that might possibly be modified elsewhere behind mutexes? ...I suppose that might actually be good practice in a lot of cases, but I shudder at the thought of the performance penalties it would impose.
(Score: 2) by barbara hudson on Thursday August 29 2019, @02:27AM
SoylentNews is social media. Says so right in the slogan. Soylentnews is people, not tech.
(Score: 4, Interesting) by bzipitidoo on Sunday August 25 2019, @07:45PM (3 children)
I use const all the time. They're a great use of global scope. Making labels for small constants helps distinguish between multiple constants that happen to have the same value.
As to performance, how about "Pascal" for a now obscure performance related C keyword? Think it was deprecated, maybe in C99.
(Score: 5, Funny) by Bot on Sunday August 25 2019, @09:11PM (2 children)
>I use const all the time
You mean, like, constantly?
Account abandoned.
(Score: 2) by bzipitidoo on Monday August 26 2019, @03:36AM (1 child)
They're my constant companions.
(Score: 2) by Bot on Monday August 26 2019, @11:09PM
i c
Account abandoned.
(Score: 3, Insightful) by Ken_g6 on Sunday August 25 2019, @05:18PM (13 children)
Const function arguments won't make code faster. On the other hand, const constants really should, such as this:
i/d should get turned into i>>2.
(Score: 2, Insightful) by Anonymous Coward on Sunday August 25 2019, @05:26PM (3 children)
JavaScript runs on the consumer end. Nobody gives a shit about how fast it because we don't fucking run it. As long as we get that sweet sweet data to the advertising server so we can sell targeted ads to the fucking idiot consumer. Nothing else matters.
(Score: 2) by maxwell demon on Sunday August 25 2019, @08:38PM (2 children)
And in which way are the properties of JavaScript relevant for the optimization of code written in C or C++?
The Tao of math: The numbers you can count are not the real numbers.
(Score: 1, Funny) by Anonymous Coward on Sunday August 25 2019, @09:25PM (1 child)
You're not getting the message, snowflake. C is irrelevant because C is not used in the real world to sell targeted advertising. JavaScript makes money.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @11:00AM
Still mining Monero on other people's computers? Tse, tse.
(Score: 2) by BsAtHome on Sunday August 25 2019, @05:38PM
In this *special* case, that would work. But only if you look at the loop variable 'i' and detect it to be strictly positive. Otherwise, shifting does not always act as you want for integers. Not all compilers distinguish between LSR and ASR. Therefore, you would want to use unsigned types, which are more narrowly defined.
The other problem is that the constant declared integer 'd' has a memory allocation in the strict sense. It is declared non-static and must therefore be visible throughout the link-space as addressable and cannot be eliminated using substitution easily. Referring to 'd' in the code therefore requires a memory dereference.
That brings us to the problem that you can take the address of 'd' (using &d) and have a (const int *) type. That means that 'd' /must/ be in addressable memory-space and cannot be substituted as a constant value in the division.
Please note that declaring a 'const int' in your code is not the same as using #define. These are two totally different beasts of constructs with each their advantages and disadvantages. Do not get them mixed up.
(Score: 2, Informative) by Anonymous Coward on Sunday August 25 2019, @06:02PM (5 children)
Well, let's see. With GCC 8.3.0, you get, (sorry, all the brackets removed, but I can't be arsed to HTML quote them for soylent crappy interface)
0x0000000000000000 : push %rbp
0x0000000000000001 : lea 0x0(%rip),%rbp # 0x8
0x0000000000000008 : push %rbx
0x0000000000000009 : xor %ebx,%ebx
0x000000000000000b : sub $0x8,%rsp
0x000000000000000f : nop
0x0000000000000010 : mov %ebx,%esi
0x0000000000000012 : mov %rbp,%rdi
0x0000000000000015 : xor %eax,%eax
0x0000000000000017 : add $0x1,%ebx
0x000000000000001a : sar $0x2,%esi ---- HERE
0x000000000000001d : callq 0x22
0x0000000000000022 : cmp $0x64,%ebx
0x0000000000000025 : jne 0x10
0x0000000000000027 : add $0x8,%rsp
0x000000000000002b : pop %rbx
0x000000000000002c : pop %rbp
0x000000000000002d : retq
and without the const,
0x0000000000000000 : push %rbp
0x0000000000000001 : lea 0x0(%rip),%rbp # 0x8
0x0000000000000008 : push %rbx
0x0000000000000009 : xor %ebx,%ebx
0x000000000000000b : sub $0x8,%rsp
0x000000000000000f : nop
0x0000000000000010 : mov %ebx,%esi
0x0000000000000012 : mov %rbp,%rdi
0x0000000000000015 : xor %eax,%eax
0x0000000000000017 : add $0x1,%ebx
0x000000000000001a : sar $0x2,%esi --- HERE
0x000000000000001d : callq 0x22
0x0000000000000022 : cmp $0x64,%ebx
0x0000000000000025 : jne 0x10
0x0000000000000027 : add $0x8,%rsp
0x000000000000002b : pop %rbx
0x000000000000002c : pop %rbp
0x000000000000002d : retq
Wow, so the same. Actually, not surprised at all. The compiler will determine if something can be changed by some lines of code and it's useless to have const. That's mostly for the programmer, not the compiler.
(Score: 0) by Anonymous Coward on Sunday August 25 2019, @08:09PM
You could have used Unicode brackets instead:[]{}<>
(Score: 2) by maxwell demon on Sunday August 25 2019, @09:07PM (3 children)
For code there is <ecode>:
The Tao of math: The numbers you can count are not the real numbers.
(Score: 0) by Anonymous Coward on Monday August 26 2019, @08:04AM (2 children)
(Score: 0) by Anonymous Coward on Monday August 26 2019, @10:53AM (1 child)
C:/>
(Score: 0) by Anonymous Coward on Monday August 26 2019, @09:32PM
You unix kids and those wrong-way slashes.
(Score: 2, Informative) by Anonymous Coward on Sunday August 25 2019, @06:02PM (1 child)
Any compiler worth it's salt should be able to identify constants and optimize accordingly regardless of whether they are declared as const or not.
(Score: 2) by FatPhil on Monday August 26 2019, @06:12AM
Great minds discuss ideas; average minds discuss events; small minds discuss people; the smallest discuss themselves
(Score: 2) by hendrikboom on Sunday August 25 2019, @08:23PM (1 child)
In the original example, it's possible for the argument x to point into variables controlling printf's output buffer, so it'a very possible for *x to change even without calling another function.
(Score: 3, Insightful) by FatPhil on Monday August 26 2019, @06:18AM
Rather than saying "const didn't help the compiler optimise maximally, so don't bother using const", the article should have been "why doesn't the compiler do the optimisation I wanted?".
This is an article about how useless cars are for getting you places, in situations where the car is up on bricks.
Worse - this article hasn't even noticed the bricks.
Great minds discuss ideas; average minds discuss events; small minds discuss people; the smallest discuss themselves
(Score: 3, Insightful) by maxwell demon on Sunday August 25 2019, @08:35PM (3 children)
I didn't have to look at the compiler output to know that the compiler cannot use const for optimization in this case. Just my general C and C++ knowledge.
Let's look at the code:
So what does this tell the compiler? Well, it tells the compiler that the function constByArg gets a pointer to a potentially constant integer, and that it should not modify that integer through that pointer. It then passes the pointer on to constFunc which makes the same promise.
However while compiling constByArg, the compiler does not know where the variable comes from, and in particular not whether it is really const, nor whether constFunc can change that variable by other means. For example, another translation unit might contain the following code:
That program is 100% legal code, and is required to output
In other words, if the compiler made the optimizations the author was obviously expecting, it would not conform to the standard.
But anyway, const isn't even meant as an optimization tool, it is meant as a tool to help with ensuring correctness of code. Just like the compiler will flag as error any attempts to multiply a pointer by 2, even though on about every modern architecture the pointer just contains a number, the compiler will also flag the attempt to modify a value through pointers to const. And just like you can bypass the first error with typecasts, you also can bypass the second error. A typecast is basically telling the compiler "I know what I'm doing, so don't complain". Of course if you do not know what you are doing, your code may blow up. But hey, if you disable the safety mechanisms, that's what you have to expect (just ask the people who operated the Chernobyl reactor!).
The Tao of math: The numbers you can count are not the real numbers.
(Score: -1, Redundant) by Anonymous Coward on Sunday August 25 2019, @11:13PM (1 child)
Assuming you got the function names wrong by accident, and you meant to put in two printfs, no shit? You weren't doing an operation on *x.
However, if you read *x, a const int *, you would have seen the value change.
(Score: 2) by maxwell demon on Sunday August 25 2019, @11:25PM
I did not get the function names wrong. If you look at the complete post, you'll find the call of constFunc, as well as the two printf calls.
The Tao of math: The numbers you can count are not the real numbers.
(Score: 2) by FatPhil on Monday August 26 2019, @07:51AM
In order for const to help provide what newbs thinks const provides, you also need to teach them restrict and __attr__((pure)) (and thefore that in order to achieve their goals they need to escape the core language, and rely on individual implementations instead), and then hammer into them the fact that if you do end up with the desired optimisations it's only a side effect of what you've actually concretely got - namely an instruction that *you* are not permitted to alter the pointed-to object (via that pointer, modulo "restrict").
It was a bad choice of word, and we're stuck with it, but that doesn't mean it's a bad concept, despite what some of the neighsayers are braying on about above.
Great minds discuss ideas; average minds discuss events; small minds discuss people; the smallest discuss themselves
(Score: 2) by darkfeline on Sunday August 25 2019, @09:26PM (1 child)
One of the nice features of Go is that it has real constants. Consts are analogous to macros in that they are rendered at compile time.
Some effects of this:
1. You can't take a pointer to a const, since a const doesn't exist in memory; its literal value is inserted where it's present in the code.
2. Numeric consts have infinite precision and are unbounded. You can make consts far greater than an int64 for example, and division is exact. Of course, if you try to assign such a const to a variable the value will get truncated.
3. The compiler can reason about consts, e.g. for pruning branches.
Join the SDF Public Access UNIX System today!
(Score: 0) by Anonymous Coward on Monday August 26 2019, @08:08AM
So does C++. They are just called constexpr
const is from the old days and like everything else legacy, it has history and "reasons". If you want real constants that are not macros, use constexpr.
(Score: 2) by cosurgi on Monday August 26 2019, @09:32AM (2 children)
> can be made slightly faster because the compiler knows that *x is constant
The compiler knows *x is constant only if you use constexpr [1], and optimizes for that. You will want to repeat all your benchmarks with this small fix. Remember that this works only if the value is known at compile time.
[1] https://en.cppreference.com/w/cpp/language/constexpr [cppreference.com]
#
#\ @ ? [adom.de] Colonize Mars [kozicki.pl]
#
(Score: 2) by turgid on Monday August 26 2019, @10:24AM
If you are coding in one of the modern and incredibly complex dialects of C++. C programmers know what they are doing,
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 0) by Anonymous Coward on Monday August 26 2019, @04:24PM
Completely untrue. In this case, const and constexpr will be equivalent.