Extreme include discipline for C++ code
blog.kowalczyk.info
blog.kowalczyk.info
Holding things by pointer on the other hand actually does let you prevent the includes since you can just forward declare in the header, but it's an indirection :-/
Then you get quickly into a situation where a header includes another header that's no longer needed by itself, but is used by another header in the same 'include tree', which in turn doesn't include the required header itself. Gets very messy very quickly.
It's much easier to notice and fix such situations in the top-level implementation file, and another advantage is that the "dependency complexity" of an implementation file is visible at a glance by looking at the include list at the top of the file.
TL;DR: It's not primarily for compilation speed, but for 'header hygiene' (but of course this will also eventually help with compilation speed as the project grows).
Another well known (at least among game devs) proponent of the the same idea is Our Machinery (granted, the whole idea makes a lot more sense in C than in C++, because in C declaration and implementation is usually much stricter separated into header and source files than in C++)
The idea is that each file (source or header) should include exactly those headers from which it uses things. In practice, it gets a bit more complicated has you don't want to include internal implementation headers and sometimes the same thing does not even have a canonical public header but IWUY does allow you to configure all that to your liking.
And it means you now need to deal with raw pointers, which often isn't optimal or preferred (over say references for example).
At least we'll get modules soon. That should reduce the need for some of these workarounds that are really mainly used to reduce compile times, and not to improve the code in any other meaningful way.
Reminds me of the good ol' days when Modula-2 had .def (definition) and *.mod2 (implementation) files, and you could compile the definition files alone to get binaries of the interface files that you could write and compile against in a typesafe way before the interfaces were even implemented
And as far as I recall the decoupling also made things compile fast (e.g. with Applications Systems Heidelberg's Modula-2 on the Atari ST 520+).
It may be possible to avoid that worst-case situation without such strict discipline, but with the no-recursive-include rule for sure it won't happen.
Of course this assumes that headers only include what they really need. If a header includes something it shouldn't (maybe an old dependency that was forgotten) the issue is old dependencies, and the fix is to remove it from the header once. With the article method you will need to remove it from every file that includes button (except maybe it is also used by another dependency and compile will fail).
Edit: I've realized the problem. If A includes B because it requires it, and C requires A and B, C will probably only include A and it will compile. Later if A is refactored and no longer requires B, removing it will break C. The solution is to always include what you use, even if it it's already included as dependency, as explained by another comment.
IME the best tools to solve the duplicated parsing (and compilation for templates) is to use IWYU [0] to cut down on unneeded includes and unity/jumbo builds to combine multiple translation units so common headers only need to be parsed once.
It would be a relatively simple thing to fix, if anyone is looking for a fun little project to learn a bit about GCC.
Errr, why?
Plus:
> I don't think I've ever seen any C++ code bases that follows this rule.
It's not particularly useful to do this given that the moment you include anything from /usr/include and STL that rule will get broken from under you a 100 times.
That said, I don't think that is that big of a problem compared to final compile time reduction.
EDIT: Disregard that, the preprocessor remembers the states of include guards. So there is literally no point.[1]
[1] https://gcc.gnu.org/onlinedocs/cpp/Once-Only-Headers.html
I would convert my entire codebase to modules in a second if the CMake Support was nicer.
https://xmake.io/#/ https://tboox.org/2021/10/30/xmake-update-v2.5.9/
Most likely that will be, simply, "import std" in almost all cases, with no reason for finer granularity.
Of course we will need to update our non-Standard libraries to get the benefits there. That will happen fast once more compilers finish implementing C++20 features.
[1]https://gitlab.kitware.com/cmake/cmake/-/issues/18355
[2]https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p16...
[3]https://devblogs.microsoft.com/cppblog/cpp20-modules-in-cmak...
I don't think it's hard to fathom that C++ modules might not gain traction when there is little indication of adoption in compilers, let alone code based that could benefit them.
You are using a snapshot frozen years ago. Surprise, frozen thing is still frozen! You are of course able to build for frozen system with modern compilers (e.g. "dev-toolset-11". If you don't, it is because, and only because, your employer has chosen not to.
My recommendation is, get a better employer.
Why the default is that horrible CentOS (I am an Ubuntu person), is beyond me.
It's been decades since Oracle stopped being relevant (only reason to ever pick up a RedHat compatible distro, IMO).
You can build for CentOS 7 in a docker image on your Amazon host, and also run Gdb on your Amazon host, using "target remote" to attach it to a gdbserver on your CentOS 7 target execution environment.
I use sshfs on my host to map a directory on the target, so my builds put binaries over there automatically.
Some 'normal' libraries still don't exist for ARM, and I have to compile more stuff myself.
My own C++ code is the one requiring GCC 5 or higher.
I don't follow any such rules for my own codebase and here are how my compilation times look like for three representative files: https://streamable.com/pboot1
- first and second one have tons of Qt / std / boost:: / etc... stuff
- third one includes opencv.hpp (which includes most opencv libraries)
I have a hard time seeing the benefit of putting more effort in this considering that the edit -> build -> run cycles takes around a second or two in all cases, and I don't even use all the clang PCH options available in recent clang versions.
So no,
> the price of minimizing compilation times is
using your damn tools correctly.
For decades, Bloomberg development, and people so unfortunate as to follow them (because of silly book from the early '90s) adhered to an idiotic convention that wrapped every #include directive in an #ifdef block. Of course, all compilers, also for decades, recognized when they had already seen an include file that itself was wrapped in its own #ifdef block, and skipped it. Later this same behavior was simplified to #pragma once, obviating the need to invent unique preprocessor names for all headers, which on occasion collided or were misspelled, with amusing results.
There are no remaining C++ compilers in use, outside of sad pre-Standard backwaters, that do not implement #pragma once.
And anyway, the proper solution if you are worried about compilation time is to switch to C++20 modules.
Of course you then find the same function prototypes repeated over and over in many headers but I don't mind the boilerplate when it is automated and I don't have to actually write any of it.
The only problem is that it is bare bones and only supports outputting the header file in the same directory than the c files, that's mildly annoying but that could easily be modified and the program is contained in a single c file.
https://fossil-scm.org/home/doc/trunk/tools/makeheaders.html
https://github.com/include-what-you-use/include-what-you-use
I'm currently having to fight against the tide of the very-broken way that the codebase was built ... very much _not_ as a modern or even old school project let alone a Docker-based project. So tons of things get included everywhere and some things are even compiled multiple times. It's a bit of a nightmare.
Putting tooling into CI will help prevent problems from showing up ... but the tooling wasn't there from the start so most of the project needs to be refactored so that CI doesn't immediately turn red. And that's the biggest headache tbqh
That's a great idea. But I'm not sure how to easily do that. Getting CI to fail if clang-format reports a warning is easy enough. But... you suggest that I should store all of the existing warnings somewhere and only report new warnings? That's a lot (!) more effort unless you know an easy way
So you're manually doing what the compiler and code pattern used to do for you. And creating dependencies in your source code to files that may become obsolete (include bar.h because foo.h needs it; then foo.h changes and now bar.h is no longer needed yet you still include it)
I have a different code pattern: header files include everything they need and no more; I include just the API's I use in my source files. I prefer this pattern by leaps and bounds over the OP's recommended technique. Because the source and tools manage themselves.
Yes, including the one posted by the author, sounds great on paper but in practice provides next to no benefit. Notice how the author has provided absolutely zero evidence to justify their claim.
>You just included and parsed bar.h twice.
No you didn't, because header files have header guards of the form #ifndef HEADER_GUARD/#define HEADER_GUARD or #pragma once which means that header files are only parsed the first time they are included.
>This makes me either a madman or a genius.
It makes you neither, you're just following something because someone with a big name who you respect said it, so you to do it without actually verifying for yourself whether it's true.
>Name an economically successful communist country.
China.
The header file still as to be read from disk ignoring everything until #end and then continue. This is probably not much of an issue these days with SSDs and even HDDs with caches.
I think the rule can still be a good thing by making you aware of what the dependencies actually are, which can be an indicator of excess complexity or poorly defined interfaces.
Anybody trying to optimize on top of that is engaged in foolish superstition.
(If you are still using Sun's pre-Standard compiler, you are not reading this. I think even Bloomberg has abandoned that.)
That is true and unfortunate, but it is easy to find evidence that cleaning up header files will decrease compilation times: https://lore.kernel.org/lkml/YdIfz+LMewetSaEB@gmail.com/T/
>> Name an economically successful communist country. > China.
Except that China’s success has mostly come by instituting a “Special Economic Zone” where a lot of the normal communist rules don’t apply, and then gradually relaxing the rules elsewhere as well. Just the fact that China allows individuals to start and run businesses is a huge break from communism.
Most people criticizing this article, including myself, argue that it does not (along with reasons why, such as header guards and the optimizations that compilers include to recognize them).
I reiterate that I think it is unfortunate that the author of this article collected no numbers. It would be interesting to compare them to the Linux kernel’s numbers:
| v5.16-rc7 | -fast-headers-v1
|--------------------------------|---------------------------------------
'touch include/linux/sched.h' | 230.30 secs | 15.6 builds/hour | 108.35 secs | 33.2 builds/hour | +112%
'touch include/linux/mm.h' | 216.57 secs | 16.6 builds/hour | 79.42 secs | 45.3 builds/hour | +173%
'touch include/linux/fs.h' | 223.58 secs | 16.1 builds/hour | 85.52 secs | 42.1 builds/hour | +161%
'touch include/linux/device.h' | 224.35 secs | 16.0 builds/hour | 97.09 secs | 37.1 builds/hour | +132%
'touch include/net/sock.h' | 105.85 secs | 34.0 builds/hour | 40.88 secs | 88.1 builds/hour | +159%
Doubling and nearly tripling the compile speed is a huge win!But the really eye–opening numbers are further down. Here are the first few rows from the table:
------------------------------------------------------------------------------------------
| Combined, preprocessed C code size of header, without line markers,
| with comments stripped:
------------------------------.-----------------------------.-----------------------------
| v5.16-rc7 | -fast-headers-v1
|-----------------------------|-----------------------------
#include <linux/sched.h> | LOC: 13,292 | headers: 324 | LOC: 769 | headers: 64
#include <linux/wait.h> | LOC: 9,369 | headers: 235 | LOC: 483 | headers: 46
#include <linux/rcupdate.h> | LOC: 8,975 | headers: 224 | LOC: 1,385 | headers: 86
#include <linux/hrtimer.h> | LOC: 10,861 | headers: 265 | LOC: 229 | headers: 37
#include <linux/fs.h> | LOC: 22,497 | headers: 427 | LOC: 1,993 | headers: 120
Note in particular that sched.h includes, directly or indirectly, 324 _unique_ header files. Header guards are not going to help you here, because there are still 13k lines of code to parse and compile. The speedup comes from reducing this to 64 unique headers and just 769 lines of code to compile.You cannot rely solely on #pragma once or header guards. You should definitely use them for correctness, but if that’s all you do you will leave a lot of compile–time performance on the table.
What? No the two have nothing to do with one another.
The clean up in the Linux kernel involves having more granular includes, that is breaking up very large header files on the order of 10s of thousands of lines of code that often include unrelated or independent declarations, down into smaller header files that are on the order of 100-1000 lines of code and whose declarations are tightly coupled. I don't think many people would argue against breaking down large catch-all header files into smaller independent header files so that a consumer only needs to include what they need instead of having to include everything and the kitchen sink.
>Note in particular that sched.h includes, directly or indirectly, 324 _unique_ header files. Header guards are not going to help you here...
Of course not, and no one is claiming it will. It's also not going to help you if you decide to move all of those 324 unique header files into your .c file which is all this article is suggesting you do. What will help you is to refactor your dependencies so that you don't need to include 324 header files to begin with and instead can reduce your includes down to some minimal set. Once you've broken your dependencies down to a minimal set of headers, it won't matter whether you include that minimal set in a .c file or a .h file, what matters is that you've reorganized your dependencies to avoid having a bunch of declarations that are independent of one another.
Ultimately you're conflating two very different concepts under the vague term "cleanup". Having granular header files is something many people would agree makes compile times faster and also just improves overall software quality, by all means do it. Moving all your includes from .h into .c is just some kind superficial cargo-cult programming and will have no material impact on compile times or quality.
"blog.kowalczyk.info uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.
Error code: NS_ERROR_NET_INADEQUATE_SECURITY"
I don't know what tool you're using to determine this, but it'll say the same for half of the internet as my site is hosted on render.com and proxied via cloudflare.
Also, everything on this website is open source https://github.com/kjk/blog
You can read all the "secrets" you want without even visiting the site.
I'm sure I'm missing something, but I'm curious why the compiler can't parse the header just once and keep it in memory (with further dependencies forming a graph of ASTs referencing each other), instead of naively combining all the raw text every time before it can parse the entire combined file?
If I ever did another C or C++ project (which would probably only be at gun–point), I would go the opposite route and have only a single compilation unit. I would have a single primary source file that included all of the others, and none of the others would include anything. Then I could run the compiler a single time on just that one file and it would be as fast and as simple as possible. Well, I don’t know of any C or C++ compilers that can spread their work over multiple cpus (since we usually just run multiple instances simultaneously with make -j), but other than that it would be as fast as possible.
For instance, if the file was this:
// my-file.h
#ifndef MY_FILE_INCLUDE_GUARD
#define MY_FILE_INCLUDE_GUARD
void f(void);
#endif
the compiler would remember the “#ifndef MY_FILE_INCLUDE_GUARD” part.fast compiles are nice but to trade away your code's hackability seems infinitely counter-productive to me. won't that have an immense negative impact on total development time?
Include code style is also just the tip of the iceberg. This also includes not using many c++ features that blow compile times up unless the gain from using them is so big that you eat the compile time (like containers would be an obvious example).
And as the article mentions, that doesn't always solve the problem.
by that logic no one should use python, ruby or rust because they don't have an iso or ecma standard either
This work happened in 2009. It was originally based on Ruby 1.8.7, the final draft was completed in 2010, and didn't cover the full standard library at the time. Took two more years to get through ISO.
If you don't have an encyclopedic remembering of Ruby version releases, when that draft was complete, the current released version of Ruby was 1.9.2. Ruby underwent huge changes between 1.8.7 and 1.9.2; many other languages would have characterized it as a major version change at the time; Ruby didn't follow semver back then.
So basically, it was already out of date well before it was final, and never was updated. So it's really irrelevant today.
Why did this even happen? Well, supposedly there are some requirements for Japan government work that require a spec, and so a spec was produced. That's my recollection, anyway.
The purpose of communism has never been that of being "economically successful".
If you want to dismiss communism as a failure, first off, you should not measure money, and not even wealth, but simply happiness. Second, you should measure equality, and, finally, you should measure the happiness of the least happy, not that of the most happy.
1. The doctrine those states called "Marxism" or "Marxism-Something" very much emphasized well-being as material well-being stemming from economic success.
2. Self called Socialist states, generally referred to as Communist states as they were dominated by an all powerful "communist" party, have not been in general successful in terms of general happiness, although they argued that they at least brought an industrialized level of economic prosperity whereas capitalism would have left those places stranded in misery. Wether that is true or it was worth the price is a question for historians to debate on.
To summarize : Humans don't live by bread alone, and I am not even sure the bread was great there.
Err..China ?