Does pre-compiling c++ header files slows down linking time?
Asked Answered
I

1

8

I have 100 .h files in a externalFiles diretory. Around 10 .cpp files in my source includes these .h files.

So I removed all #include externalFiles/.*h directives from my .cpp files and wrote them in a pch.h header that I include via Cmake's target_precompile_headers(${PROJECT_NAME} PRIVATE pch.h).

I checked my build times using precompiled headers via Cmake vs simply including pch.h in my .cpp files. I noted build times using: set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") and set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time")

Build time using precompiled headers

[1/2] Building CXX object CMakeFiles/AlgoCoding.dir/main.cpp.obj

Elapsed time: 11 s. (time), 11.413 s. (clock)

[2/2] Linking CXX executable AlgoCoding.exe

Elapsed time: 54 s. (time), 53.459 s. (clock)

Build time just including headers

[1/2] Building CXX object CMakeFiles/AlgoCoding.dir/main.cpp.obj

Elapsed time: 14 s. (time), 13.35 s. (clock)

[2/2] Linking CXX executable AlgoCoding.exe

Elapsed time: 28 s. (time), 28.109 s. (clock)

Conlusion

From these times it seems like using precompiled headers does reduce build time for the .obj creation of the .cpp file modified (main.cpp) but it increases the link time heavily.

Is this the case for everyone or am I doing something wrong here?

EDIT: 1

Experiment #2 to test Build times of precompiling headers.

I tried to include spdlog library(a header only library containing 100 .h files) into my bare-bones Cmake project (just a main.cpp logging 1 line into a file). Two methods of doing this.

  1. Just copy spdlog folder into my source and use #include directives in main.cpp
  2. Use precompiled headers (#include directive for spdlog in pch.h and pch.h added to project using target_precompile_headers(${PROJECT_NAME} PRIVATE pch.h))

Build Time Results

----Method 1

[1/2] Building CXX object CMakeFiles/test.dir/main.cpp.obj

Elapsed time: 22 s. (time), 21.904 s. (clock)

[2/2] Linking CXX executable test.exe

Elapsed time: 18 s. (time), 18.311 s. (clock)

----Method 2

[1/2] Building CXX object CMakeFiles/test.dir/main.cpp.obj

Elapsed time: 18 s. (time), 18.231 s. (clock)

[2/2] Linking CXX executable test.exe

Elapsed time: 22 s. (time), 21.604 s. (clock)

Conclusion

Precompiling external headers in a barebones project still gives same total build time as just using #include directive. When using precompiled headers, the small time decrease in Building .obj file is compensated by increased time Linking CXX executable.

Is there something different I need to do to reduce build time when precompiling external .h files? Or is this the expected behaviour?

Inchoation answered 23/6, 2022 at 8:29 Comment(6)
What is your linker? Assuming LD, switch to LLD for better link times.Portwine
I'm using default linker that came with mingw64 g++. I read that its (gold or GNU ld).Inchoation
That's probably LD. My advice still holds.Portwine
I'm looking into LD vs LLD. Looks like -fuse-ld=lld flag to g++ changes linker in gcc v9.3.0+. I'll report back any improvements, if any, with LLD tomorrow.Inchoation
You might also want to try Clang instead of GCC. I've got a major speedup out of it on Windows.Portwine
And see if both the times scale linearly with the number of object files using the precompiled header. If the linking time addition is a fixed slowdown, the overall effect for large projects might still be positive.Ferro
I
1

Clang+LLD vs gcc+LD

As suggested by @HolyBlackCat I tried the same compilation with clang + LLD linker. The Build times have definately improved for both normal #include of header files and precompiled headers (~100 .h files):

-------Method 1(normal #include)
[1/2] Building CXX object CMakeFiles/test.dir/main.cpp.obj
Elapsed time: 7 s. (time), 6.997 s. (clock)
[2/2] Linking CXX executable test.exe
Elapsed time: 9 s. (time), 9.493 s. (clock)

-------Method 2(precompiled headers)
[1/2] Building CXX object CMakeFiles/test.dir/main.cpp.obj
Elapsed time: 5 s. (time), 4.515 s. (clock)
[2/2] Linking CXX executable test.exe
Elapsed time: 9 s. (time), 9.076 s. (clock)

From these results, Clang+LLD performs significantly faster than gcc+LD/gold. Moreover, precompiled headers manages to reduce overall build time when used with Clang+LLD, as it reduces build time of .obj files but manages to acheive similar linking time. (See question Edit 1, for build times of gcc+default Linker)


Scaling

Additionally, @Marco van de Voort mentioned to check how scaling the number of .obj/.cpp files using the precompiled headers affected the build times. So for Method 2(precomiled headers) I compiled with 3 .cpp files (Scaling 3) and 5 .cpp files (Scaling 5). Here are the results:

-----Method 2 Scaling 3 .cpp
Building all 3 CXX
Elapsed time: 7 s. (time), 6.767 s. (clock)
[4/4] Linking CXX executable test.exe
Elapsed time: 27 s. (time), 27.404 s. (clock)

-----Method 2 Scaling 5 .cpp
Building all 5 CXX object
Elapsed time: 11 s. (time), 11.416 s. (clock)
[6/6] Linking CXX executable test.exe
Elapsed time: 47 s. (time), 46.942 s. (clock)

We can see the time increase in Linking is linear ~(9 * #cpp files to link to). For Method 1 scaling gives worse results.


Going Ahead

Although precompiled headers give better build times when used with Clang+LLD, its better to use Method 3. Find a library version(.lib/.dll) of the header only library you are trying to use. This library is statically/dynamically linked and the Build times are much faster this way.

Method 3: Using spdlog library in Cmake (installed via vcpkg):

set(CMAKE_PREFIX_PATH "<PathToVcpkg>\\installed\\x64-mingw-static") # THIS HAS TO COME BEFORE THE PROJECT LINE
project(test)
find_package(spdlog REQUIRED);  
add_executable(${PROJECT_NAME} ${project-targets})  
target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog )  

Significantly improved build times of using .lib library version (with PCH includes) compared to using header only libraries (with PCH includes):

-----Method 3 Scaling 1 .cpp
[1/2] Building CXX object CMakeFiles/test.dir/main.cpp.obj
Elapsed time: 2 s. (time), 2.073 s. (clock) [2/2] Linking CXX executable test.exe
Elapsed time: 10 s. (time), 10.294 s. (clock)

-----Method 3 Scaling 5 .cpp
[5/6] Building CXX object CMakeFiles/test.dir/main2.cpp.obj
Elapsed time: 2 s. (time), 2.244 s. (clock) [6/6] Linking CXX executable test.exe
Elapsed time: 12 s. (time), 12.024 s. (clock)


Conclusion

  • Precompiling header only libraries gives better build times when compiled with Clang+LLD.
  • Using a (.lib/.dll) library version will be the fastest way, if that library version is available.
Inchoation answered 24/6, 2022 at 17:25 Comment(4)
How about both Clang+LLD+PCH and a non-header-only library?Portwine
Method 3 is exactly that. It uses Clang+LLD for .lib version of non-header-only spdlog. Generally people use PCH for adding <iostream>,etc but the bare-bones main.cpp doesn't include any header files. P.S. Scaling tests are also on Clang+LLDInchoation
@Portwine Method 3 wasn't precompiling the simple header files of .lib version of spdlog. But I updated the results. As expected, it improved creation time of .obj but linking time is almost same.Inchoation
I'm really satisified that I explored this, as Clang+LLD+PCH + knowing how to use .libs is going to increase development speed. But enough of this profiling :D . Time to work on actual application nowInchoation

© 2022 - 2024 — McMap. All rights reserved.