Experiments With Includes
By Kyle Wilson
Sunday, January 09, 2005
I'm co-authoring Day 1's new coding standard for our next project right now, and in the interest of having a firm scientific basis for my recommendations, I did a few experiments with how one header file includes another. I wanted to compare compile times for three different scenarios:
- Header files including other header files with no special optimizations.
- Header files including other header files, but with each header declaring #pragma once.
- Header files including other header files, but with external include guards.
External include guards, or redundant include guards, were an innovation suggested by John Lakos in his Large Scale C++ Software Design. Much of the time spent compiling the typical C++ program is actually spent opening all the header files included by any cpp file. Precompiled headers and platform-specific extensions like Visual C++'s "#pragma once" are mechanisms for reducing this time. Redundant include guards are another means of accomplishing the same thing as #pragma once. The idea is that you standardize your include guard format, so that if you had a file named Filename.h, for instance, you'd guard it with:
#endif // FILENAME_H_
Then to include Filename.h, you'd include it with:
#endif // FILENAME_H_
Herb Sutter, C++ guru and current chair of the ISO C++ standards committee, argues against external include guards:
"Incidentally, I strongly disagree with Lakos' external include guards on two grounds:
1. There's no benefit on most compilers. I admit that I haven't done measurements, as Lakos seems to have done back then, but as far as I know today's compilers already have smarts to avoid the build time reread overhead--even MSVC does this optimization (although it requires you to say "#pragma once"), and it's the weakest compiler in many ways.
2. External include guards violate encapsulation because they require many/all callers to know about the internals of the header -- in particular, the special #define name used as a guard. They're also fragile--what if you get the name wrong? what if the name changes?"
Sutter may not have done measurements, but other people have. Jeff Grills, tech director on Star Wars Galaxies, posted to GD-General that "I once wrote a perl script to implement Lakos' include guard techniques on all of our source, and it had no significant impact on our build times in MSVC 6." Noel Llopis did some experiments on the MechAssault 2 code base in March of 2004, changing one of our libraries to see its compile time with redundant include guards and with #pragma once. He reported no measurable gains with either technique.
My experiment was slightly different. Instead of testing on an existing co debase, I created a test project of 200 header files, every one of which included all the others, and a single cpp file that includes all headers and an empty main function. I tested timing by turning on build timing in Visual Studio (Tools -> Options -> Projects -> VC++ Build -> Build Timing), then by right-clicking on the main.cpp file and selecting compile. My timings were as follows:
- Nothing - 28 seconds
- #pragma once - 17 seconds
- Redundant include guards - < 1 second
I'm puzzled by these results on two accounts. First, as I confirmed by turning on "show includes" in the project settings, the redundant include guard project includes more headers than the #pragma once project, yet compiles in a small fraction of the time. Second, the other reports I've read have claimed that neither #pragma once nor redundant include guards produced significant speed-ups. In my test, both changes resulted in significant compile speed improvement.
Both the other experiments I mentioned above were done on actual game engines, not worst-case test projects. I hypothesize that in a real game engine, more time is spent doing actual compilation and linking, and include time is reduced with the use of precompiled headers, so that the added benefit of #pragma once or redundant include guards is reduced.
Why a project with redundant include guards compiles so quickly relative to a project with #pragma once continues to baffle me. Perhaps it has something to do with the different mechanisms by which Visual Studio resolves #pragma once commands and #define macros. Importing my test into the beta version of Visual Studio 2005 similar relative timings.
For now, our coding standard will advise that #pragma once be included in every header file. For the reasons Sutter cites, I can't bring myself to recommend redundant include guards, despite their obvious benefits.
Anyone wishing to repeat my experiment is welcome to the Visual Studio projects that I used.
Any opinions expressed herein are in no way representative of those of my employers.