This can be accomplished more simply and reliably by marking modulus as const. The compiler currently has to reason about the whole compilation unit to determine that modulus is not modified, which works. However, if future code modifies modulus (either on purpose or accidentally) or something changes that prevents the compiler from performing global reasoning, the optimization will be lost. By marking the actual intention, any modifications of modulus turn into compiler errors. Plus, if it becomes important to expose modulus to another compilation unit, now that's possible.
This is a common issue with C code (including lots of code I've written). It's really easy to forget to const something, which forces the compiler to do global reasoning or to generate worse code. I've gotten into the habit of making things const unless I know I plan on mutating them, but I wish there was tooling that encouraged it. (BTW, this is something Rust does well by making things constant by default and requiring "mut" if it's mutable.)
I agree. Static alone was the incorrect choice. If the global were being modified elsewhere in the file then this optimization would also not be possible.
The core optimization is modulus % constant (and a power-of-2 as well). The static just enabled the optimizer to do better heavy lifting to get there. A const would've made the intent clear to the human reader and the compiler.
You'd think the compiler would let you know you have a constant that is not labelled as such, like the way `tslint` complains about this incessantly. (I think `splint` for c/c++ may also do this, but I've only briefly used it.)
The compiler only operates on one unit (file) at a time so it has literally no way of telling this in C. There are legitimate uses for having a non-const global which is never modified by local source: library config options, hooks for external programs, and what not. As you say, this would be a job for the linter.
You can get pretty far with a compiler warning like "warn if a global isn't preceded by an 'extern' declaration". Also, LTO does have enough information to warn about these things, especially with default hidden visibility.
If there were any expressions that took the address of the variable, then even both `static const` qualifiers wouldn't work for a sufficiently paranoid compiler.
This is why language standards specify what the compiler can assume and call out some behavior as undefined, exactly so compilers don't have to be paranoid and produce code that sucks. If an underlying object is const, the compiler is allowed to assume that it does not change (it is valid to cast away const on a pointer or reference, but not if the object itself was declared const).
Author here -- I agree that const is better. The perf difference I encountered was due to the static keyword though, which is why the blog post talks about that specific issue.
The perf difference was due to the compiler being able to infer 'const' by way of 'static'.
Advocating 'static' when your actual intent is 'const' does less experienced readers a disservice; they will assume that 'static' is meant to make things faster, and be disappointed when it doesn't work for non-constant values.
I am not advocating static. I'm advocating for looking at what the compiler outputs when surprising behavior is encountered. The example is extracted from a larger piece of code, and I reported the minimal case as-is.
If anything, the title is meant to be read as "isn't it amusing that something apparently unrelated such as `static` causes a performance improvement".
That said, I have added a note clarifying this at top of the post now.
I thought you made your point neatly and succinctly. Code can be optimized on multiple levels; the compiled result is clearly more efficient. This lesson applies regardless of the language being compiled.
I started to use const whenever possible after being familiar with some compiler optimizations and the Haskell pl. The point is knowing how to give the compiler an easier job.
It's really easy to forget to const something, which forces the compiler to do global reasoning or to generate worse code.
Global mutable state is pure evil. Don’t write globals.
It shouldn’t be easy to forget const on a global because a mutable global should produce immediate revulsion and nausea.
(I don’t really consider a const global to be “a global”. So ordinarily I’d just say globals are evil don’t write globals. But I’m trying to be explicit here.)
This is one of those sayings that I don't think is helpful. What it should be is: limit variable scope to the smallest thing it can be.
Nobody is being fooled about global state when you have a singleton database connection, event bus router, or network stack. I don't think your program is better when you pass in i/o functionality to every single class context in the constructor.
Similarly a mega-class that encapsulates everything your program does is also a code smell. There's no point to a private variable when everything can access it.
I don't think your program is better when you pass in i/o functionality to every single class context in the constructor.
Abstracting over I/O transport is an excellent thing to do. This allows you to do things like easily record and replay a network stream. Which is useful for both debugging and automated tests.
I/O comes in a kazillion flavors. Networked, interprocess, serial port, file, synthetic, etc etc. It's definitely something that should be abstracted around and not doing so is something I've deeply regretted in the past.
a mega-class that encapsulates everything your program does is also a code smell
Ok I agree it can have a foul odor. But even this can be advantageous.
Once upon a time Blizzard gave a GDC presentation about Overwatch. Kill-cam replays are notoriously difficult in video games.
Blizzard's solution to this was delightfully elegant. They made two copies of their world. One perpetually runs on latest. One takes snapshots of the world every N frames. When a player dies their viewport switches to the old snapshot which then simulates and renders for ~6-10 seconds. When the replay finishes or skips the viewport switches back to the main game, which never stopped receiving updates. This was a relatively trivial implementation given the complete lack of globals and singletons.
A mega-class lets you run parallel instances of your "world". It's also a nice pattern when you want to build-up and tear-down your world in-process and guarantee no stale state. For example when running tests you likely want certain tests to "start clean". It's nice to be able to do this without restarting the entire process.
I'll double-down that globals are evil. They are a sometimes necessary evil. Or the least bad choice. But my experience is that not using globals is almost always simpler, more elegant, more flexible, and ultimately preferable.
There is one common situation where not using mutable globals is a recipe for complexity and a serious code smell. That case is when your internal structures directly control the physical resources of the machine. There is no amount of window dressing that can make these anything but global structures because that is what they literally are. That gets hidden a bit if you delegate resource management to the OS but you can’t do that if you care about performance.
And if you are creating and scheduling all of your concurrency in user space, which is common for some types of server software, then passing what are effectively global objects down the call stack becomes a real mess and introduces a number of suboptimal behaviors in the code gen.
I’ve seen people try to design database engines, the high-performance kind that directly manage all the resources they use, that don’t use globals in a misguided attempt to adhere to this heuristic. The end result was a convoluted mess of indirection that just obscured the reality that all of those objects were mutable globals. If you care about performance then I/O isn’t very abstract; the code knows exactly what kind of device it is dealing with.
I don’t like mutable globals as a general rule, but for some types of software they are unambiguously the correct engineering choice and not using them would be a design defect.
There are definitely resources which are globally unique. But that does not necessarily follow that access should also be global.
Rust has some elegant patterns when working with embedded devices. For example GPIO pins could totally be stateful globals. But instead their passed around as types and Rust’s type system + borrow checker ensure correctness. It’s pretty neat.
I’ll assume you’re right for databases. My expertise is real-time VR video game type stuff. Which is also high-performance, but of a different variety.
I strongly agree that layers of abstraction compound into convoluted and inscrutable garbage. I loathe web development for this very reason.
Rust struggles with software where most of the address space is used for DMA, like many database engines, because it requires ownership and mutability to be observable at compile-time. Also, DMA often does not respect object boundaries as a compiler sees it because DMA does not understand objects.
In modern C++ it is straightforward to write wrappers in the style of unique_ptr that safely hide the DMA and life cycle mechanics, which means the average dev using them doesn’t need to know how it works, but someone has to write that code and it is necessarily global heavy because it references physical devices that have their own behavior in your address space. Under the hood, if a physical device is stomping on address space your code accesses, you need a way to both detect that an object is effectively owned by a particular DMA engine before touching it and immediately de-schedule the thread of execution until such a time as there is no concurrent DMA operation that might conflict with the code execution. This happens within a single thread, so no blocking or OS context switching.
A big part of database kernel internals is coordination and management of physical resources, which are global by nature.
I agree with everything you said. Global variables also make it much harder to use multithreading. The mega-class is only bad if many parts of the code require references to it. In a well-structured program most subsystems will only need references to a few of the mega-class's (transitive) members.
Comments
This can be accomplished more simply and reliably by marking modulus as const. The compiler currently has to reason about the whole compilation unit to determine that modulus is not modified, which works. However, if future code modifies modulus (either on purpose or accidentally) or something changes that prevents the compiler from performing global reasoning, the optimization will be lost. By marking the actual intention, any modifications of modulus turn into compiler errors. Plus, if it becomes important to expose modulus to another compilation unit, now that's possible.
This is a common issue with C code (including lots of code I've written). It's really easy to forget to const something, which forces the compiler to do global reasoning or to generate worse code. I've gotten into the habit of making things const unless I know I plan on mutating them, but I wish there was tooling that encouraged it. (BTW, this is something Rust does well by making things constant by default and requiring "mut" if it's mutable.)
I agree. Static alone was the incorrect choice. If the global were being modified elsewhere in the file then this optimization would also not be possible.
The core optimization is modulus % constant (and a power-of-2 as well). The static just enabled the optimizer to do better heavy lifting to get there. A const would've made the intent clear to the human reader and the compiler.
static const would've been best.
You'd think the compiler would let you know you have a constant that is not labelled as such, like the way `tslint` complains about this incessantly. (I think `splint` for c/c++ may also do this, but I've only briefly used it.)
The compiler only operates on one unit (file) at a time so it has literally no way of telling this in C. There are legitimate uses for having a non-const global which is never modified by local source: library config options, hooks for external programs, and what not. As you say, this would be a job for the linter.
You can get pretty far with a compiler warning like "warn if a global isn't preceded by an 'extern' declaration". Also, LTO does have enough information to warn about these things, especially with default hidden visibility.
The global must be declared without "extern" somewhere or else no memory is allocated for it.
LTO could handle this if you're compiling an executable, but not a library.
That's not the correct distinction, that's why I said default vs hidden visibility.
Libraries typically export more symbols but executables can also export them eg for plugins.
If there were any expressions that took the address of the variable, then even both `static const` qualifiers wouldn't work for a sufficiently paranoid compiler.
Are you sure? Isn't it UB to modify a const object? It will probably end up in a non-writable memory page.
EDIT: gcc seems to agree with me: you can see the optimized version here[1] and the unoptimzed version if you remove "const".
[1] https://godbolt.org/z/KWrW45rK8
This is why language standards specify what the compiler can assume and call out some behavior as undefined, exactly so compilers don't have to be paranoid and produce code that sucks. If an underlying object is const, the compiler is allowed to assume that it does not change (it is valid to cast away const on a pointer or reference, but not if the object itself was declared const).
Isn't it valid to cast to non-const for a const, but only invalid to modify the const through the casted pointer?
Interestingly GCC will still optimize the loop function even if you have code that modifies the modulus. https://godbolt.org/z/EE9PnrY7s
That code is invalid, it would give a compiler warning and possibly a runtime exception.
Const is to state that this module is not allowed to modify. Think on what'const volatile int foo' means.
Mostly seen in embedded space.
i think this is most compilers.
Author here -- I agree that const is better. The perf difference I encountered was due to the static keyword though, which is why the blog post talks about that specific issue.
The perf difference was due to the compiler being able to infer 'const' by way of 'static'.
Advocating 'static' when your actual intent is 'const' does less experienced readers a disservice; they will assume that 'static' is meant to make things faster, and be disappointed when it doesn't work for non-constant values.
I am not advocating static. I'm advocating for looking at what the compiler outputs when surprising behavior is encountered. The example is extracted from a larger piece of code, and I reported the minimal case as-is.
If anything, the title is meant to be read as "isn't it amusing that something apparently unrelated such as `static` causes a performance improvement".
That said, I have added a note clarifying this at top of the post now.
I thought you made your point neatly and succinctly. Code can be optimized on multiple levels; the compiled result is clearly more efficient. This lesson applies regardless of the language being compiled.
I started to use const whenever possible after being familiar with some compiler optimizations and the Haskell pl. The point is knowing how to give the compiler an easier job.
Its better to make it constexpr than const or static.
Global mutable state is pure evil. Don’t write globals.
It shouldn’t be easy to forget const on a global because a mutable global should produce immediate revulsion and nausea.
(I don’t really consider a const global to be “a global”. So ordinarily I’d just say globals are evil don’t write globals. But I’m trying to be explicit here.)
This is one of those sayings that I don't think is helpful. What it should be is: limit variable scope to the smallest thing it can be.
Nobody is being fooled about global state when you have a singleton database connection, event bus router, or network stack. I don't think your program is better when you pass in i/o functionality to every single class context in the constructor.
Similarly a mega-class that encapsulates everything your program does is also a code smell. There's no point to a private variable when everything can access it.
I respectfully disagree on both points.
Abstracting over I/O transport is an excellent thing to do. This allows you to do things like easily record and replay a network stream. Which is useful for both debugging and automated tests.
I/O comes in a kazillion flavors. Networked, interprocess, serial port, file, synthetic, etc etc. It's definitely something that should be abstracted around and not doing so is something I've deeply regretted in the past.
Ok I agree it can have a foul odor. But even this can be advantageous.
Once upon a time Blizzard gave a GDC presentation about Overwatch. Kill-cam replays are notoriously difficult in video games.
Blizzard's solution to this was delightfully elegant. They made two copies of their world. One perpetually runs on latest. One takes snapshots of the world every N frames. When a player dies their viewport switches to the old snapshot which then simulates and renders for ~6-10 seconds. When the replay finishes or skips the viewport switches back to the main game, which never stopped receiving updates. This was a relatively trivial implementation given the complete lack of globals and singletons.
A mega-class lets you run parallel instances of your "world". It's also a nice pattern when you want to build-up and tear-down your world in-process and guarantee no stale state. For example when running tests you likely want certain tests to "start clean". It's nice to be able to do this without restarting the entire process.
I'll double-down that globals are evil. They are a sometimes necessary evil. Or the least bad choice. But my experience is that not using globals is almost always simpler, more elegant, more flexible, and ultimately preferable.
There is one common situation where not using mutable globals is a recipe for complexity and a serious code smell. That case is when your internal structures directly control the physical resources of the machine. There is no amount of window dressing that can make these anything but global structures because that is what they literally are. That gets hidden a bit if you delegate resource management to the OS but you can’t do that if you care about performance.
And if you are creating and scheduling all of your concurrency in user space, which is common for some types of server software, then passing what are effectively global objects down the call stack becomes a real mess and introduces a number of suboptimal behaviors in the code gen.
I’ve seen people try to design database engines, the high-performance kind that directly manage all the resources they use, that don’t use globals in a misguided attempt to adhere to this heuristic. The end result was a convoluted mess of indirection that just obscured the reality that all of those objects were mutable globals. If you care about performance then I/O isn’t very abstract; the code knows exactly what kind of device it is dealing with.
I don’t like mutable globals as a general rule, but for some types of software they are unambiguously the correct engineering choice and not using them would be a design defect.
Maaaaybe.
There are definitely resources which are globally unique. But that does not necessarily follow that access should also be global.
Rust has some elegant patterns when working with embedded devices. For example GPIO pins could totally be stateful globals. But instead their passed around as types and Rust’s type system + borrow checker ensure correctness. It’s pretty neat.
I’ll assume you’re right for databases. My expertise is real-time VR video game type stuff. Which is also high-performance, but of a different variety.
I strongly agree that layers of abstraction compound into convoluted and inscrutable garbage. I loathe web development for this very reason.
Rust struggles with software where most of the address space is used for DMA, like many database engines, because it requires ownership and mutability to be observable at compile-time. Also, DMA often does not respect object boundaries as a compiler sees it because DMA does not understand objects.
In modern C++ it is straightforward to write wrappers in the style of unique_ptr that safely hide the DMA and life cycle mechanics, which means the average dev using them doesn’t need to know how it works, but someone has to write that code and it is necessarily global heavy because it references physical devices that have their own behavior in your address space. Under the hood, if a physical device is stomping on address space your code accesses, you need a way to both detect that an object is effectively owned by a particular DMA engine before touching it and immediately de-schedule the thread of execution until such a time as there is no concurrent DMA operation that might conflict with the code execution. This happens within a single thread, so no blocking or OS context switching.
A big part of database kernel internals is coordination and management of physical resources, which are global by nature.
I agree with everything you said. Global variables also make it much harder to use multithreading. The mega-class is only bad if many parts of the code require references to it. In a well-structured program most subsystems will only need references to a few of the mega-class's (transitive) members.