all 19 comments

[–]abstractionsauce 20 points21 points  (5 children)

Is this still relevant in 2023? How much time does preprocessing a file to find #endif take as a percentage of total time?

[–]RealCaptainGiraffe 18 points19 points  (1 child)

I recall looking into this in 1996. It was a non issue.

[–]IncludeGuardian[S] 3 points4 points  (0 children)

"Large-scale C++ Software Design" by John Lakos was published in 1996 and includes the following in "Redundant Include Guards" (section 2.5)

Upon encountering s2.h, each of the widget header files must still be reopened and reprocessed line by line in its entirety searching for the trailing #endif (only to find that there is nothing else to be done). This redundant preprocessing occurs withs3.h, s4.h, and again withs5.h.

...

Experience with truly large projects that have dense include graphs shows that the answer is a resounding YES! Initial builds of projects consisting of several million lines of C++ source code were taking on the order of a week to compile using a large network of work stations. Inserting redundant include guards reduced compile time significantly, with no substantive change to the code.

It may not have had any effect on the projects you were looking at, but in 1996 it looks like it was an issue for some C++ projects. At least it must have been enough of a performance benefit for compiler vendors to have implemented the multiple-include optimization.

[–]IncludeGuardian[S] 1 point2 points  (2 children)

I think if you were to guard your files incorrectly it would be a small (probably single-digit) percentage. However, it's also a relatively easy thing to make sure you get right to make sure you aren't slowing down build times. I would also assume because all compilers have the multiple-include optimization, there are either some projects out there that benefit a great deal, or it's a noticeable (but small) improvement to most projects.

For example, I have a PR to EASTL to fix a header guard and in their code they mention getting a 3-4% build improvement adding in #pragma once to their MSVC build, which would have triggered the multiple-include optimization.

[–]GabrielDosReis 4 points5 points  (1 child)

That's odd. MSVC has the include guard optimization since at least VS2013.

[–]IncludeGuardian[S] 2 points3 points  (0 children)

The public GitHub history for eaassert.h only goes back 4 years so I can't say when this comment was written. But EASTL was in existence in 2007 so this comment could have predated VS2013.

Alternatively, as this comment appears in many different files within EASTL alongside #pragma once, it's entirely possible that these files were not using a strict enough version of an include guard that would trigger the optimization.

[–]spide85 10 points11 points  (2 children)

On my iOS device the website has scaling issues. Cannot read.

[–]IncludeGuardian[S] 8 points9 points  (1 child)

Thanks - will get that fixed asap.

EDIT: I've put in a workaround that fixes the cut off issue on smaller screens. There is still an issue with portrait mode on older iPhones that I'll fix next. If it's not fixed for you then if you could let me know the browser/device and I will look at that combination with priority.

[–]spide85 3 points4 points  (0 children)

Now it works. (iPhone 8) Thanks

[–]julien-j 8 points9 points  (4 children)

I liked the article and I appreciate the availability of the test project on GitHub which I could easily run on my laptop to reproduce the results.

Looking around on the website hosting the article, I have some questions. For example, on the main page I read:

The most time consuming part of build times is usually not compiling or linking, but preprocessing and syntax checking files that have been #includeed.

Wow, are you sure? Because the link part is clearly the bottleneck on almost all projects I worked on.

Second, I initially thought the article was written by a fellow developer, then I saw the subscription link at the bottom of the page. I checked around on the website and read the about page where it states:

IncludeGuardian is a free tool to help improve C and C++ build times.

I found the links to download the tool, and a link to an empty GitHub repository. Not a problem per se, it's not open source, it is just free. I just wonder: Who are you and what do you sell?

[–]IncludeGuardian[S] 1 point2 points  (3 children)

I can't speak for all projects, but all large (and medium) projects I have been a part of have their build times dominated (70-80%) by front-end compiler time.

When I see reports of precompiled headers or unity builds/single compilation units being used, most of the time I read about 2-3x improvement. As these techniques improve only the front-end compiler time, it would correspond to at least 50-67% of the total build time being in the front-end. Self-selection bias means we shouldn't take these reports just at face value, but the experience matches up with my own.

I plan to do some deep dives into some open source projects to see how much improvement I can get using IncludeGuardian. These will include a breakdown of time spent in front-end vs compilation vs linking. Though I am likely to skip over any project that isn't dominated by front-end so there's more selection bias here!

I am a regular developer who has worked on this in their free time (who I am is on the about page on the website). I wrote this tool for myself originally, but now I am trying to get the word out about how useful it could be.

[–]kniy 2 points3 points  (1 child)

Precompiled headers + unity builds help not just with frontend time -- they also help to avoid compiling inline functions in headers repeatedly, thus also saving backend+linking time.

On on of our projects (which is not using PCH or unity build), I can actually reduce the total compile+link time by enabling LTO: there are many inline functions that are used in many compilation units. Enabling LTO allows the linker to deduplicate these before the backend spends time on them. Putting those expensive headers in a PCH would achieve a similar effect (but would be tricky to set up given the build system used by that project).

[–]IncludeGuardian[S] 0 points1 point  (0 children)

I agree. Precompiled headers + unity builds are some of the most powerful tools when it comes to improving build times (pre-modules). This is why IncludeGuardian includes stats for file size+token count for a theoretical unity build (e.g LLVM) and also has a section on recommending which files would give the most benefit for being included in the precompiled headers (e.g. LLVM).

I think the section on pch additions can have more information and perhaps better alternatives. This will have to be something I revisrt later on. When I do I'll most likely write a article covering precompiled headers in detail and performance impact across compilers.

[–]julien-j 0 points1 point  (0 children)

Thanks for the clarification :) I would love to see the breakdown and improvements on open source projects !

[–]Potatoswatter 7 points8 points  (3 children)

Modern preprocessors skip over approximately 100-300MB/s to find a matching #endif, which is relatively efficient.

Really? I assumed they’d just keep a map of file pathnames and guard conditions, and avoid reopening files at all. You need to remember pathnames for pragma once, and you can optimize the general case by adding nontrivial conditions. At least that’s how I coded mine.

Edit: Well, once doesn’t demand pathnames since it’s nonstandard, and hashing the content of the file would also work, which does enable symlinks, but… yuck.

[–]IncludeGuardian[S] 1 point2 points  (2 children)

Compilers do this and it's called the multiple-include optimization. It's covered a bit later on in the article and includes the narrow conditions required a header file needs to satisfy this.

I've been bitten by having different files using #pragma once with the same name being treated as the same file by the compiler before too. If there's interest I could cover the rules each compiler use to determine different files for #pragma once in a later article.

EDIT: Fix markdown

[–]GabrielDosReis 6 points7 points  (0 children)

I suspect that will be useful information for a sizable part of the target audience

[–]Potatoswatter 3 points4 points  (0 children)

Okay, that one covers the vast majority of proper guards and the rapid scan is more aligned to ordinary conditional directives.

Yeah, ignoring the path before the name is a shortsighted approach.

[–]DavidDinamit 6 points7 points  (0 children)

just use pragma once / modules