It’s a problem, or is it?
A large multinational customer (who will go unnamed) once told me this – we have a Perl script that builds our C++-based medical imaging platform. The script is called medmake, but our engineers call it “mad make” because it takes forever. ?
I also know about a small shipping company that abandoned their perfectly working C-based software because there was nobody who could maintain their custom-built build system. Yes, one of the co-founders thought he can better Make. What a tragedy!
Does your C++ compile take ages to complete? Are you frustrated about your productivity loss due to increased C++ compile time? I have this blog post especially for you! If you are looking for more in-depth details, I suggest you check out our guide on this topic.
But… Are You Not in the Business of Speeding up Compilation?
Yes, we are. But we believe we are in a different league altogether. If you try out our solution you will understand why Incredibuild is considered the market leader in speeding up compilation times. This guide is the distilled wisdom of our years of working with C++ and alleviating the pain of our customers with it.
Do You Know Why C++ Builds Are Taking So Long?
C++ is a complex language. Its parsing is as difficult as trying to understand the sentence: “Time flies like an arrow; fruit flies like a banana”. Scott Meyers, an authority in C++, coined the term “most vexing parse” which you can read about here. Just take my word that in C++, vexing parses abound. Throw in compiler optimizations to the mix and you get a heady cocktail which just makes the build forever to complete … or instantly makes you high.
Ok, Tell Me What I Can Do.
- Unshackle the bounding resource. A build can be CPU bound (multiple compilations happening parallelly), Network bound (trying to fetch build artifacts from a repository) or Disk bound (trying to save build artifacts to the local machine). You need to identify the bounding resource in your specific case. An upgrade to a 1Gbps network from 100 Mbps greatly reduced the C++ compile time in a case I know of.
- Reduce unnecessary dependencies. Dependency Inversion might be a well-known SOLID principle, but to reduce build times you need to practice dependency elimination. Are you including header files that are unnecessary for a compilation unit? Worse still, are you including header files inside header files that are not necessary? Time to mend your ways. Use tools like include-what-you-use to remove dependencies in existing codebases. You can check out the blog on “Efficient parallel builds” to see how to use this excellent tool.
Related: About C++ Dependency Management
- Use a more modern compiler. Compilers are getting better all the time. They are getting efficient at doing better optimizations and generating better code than ever before all the while taking less time for compilation. Changing the older compiler to a newer one will improve C++ compile time for sure.
- Understand your configuration management system and its options. Let us say you use IBM ClearCase as your configuration management system. Getting a snapshot view will always be efficient for your builds than going for a dynamic view. Having MultiSite VOBs with VOBs being closer to your location can also improve the build performance.
- Use precompiled header files. Who doesn’t love precompiled headers? The idea is both simple and powerful. Rarely modified headers are kept in an intermediate representation that speeds up the build. In local compilations, this speeds up the build without having any side effects. But in distributed builds, instead of building multiple compilation units in parallel, precompiled headers aggregate the units thereby reducing the efficiency. You have been warned.
- Use the pImpl idiom. It is not for nothing that this idiom is also called a compile-time firewall. Hiding the implementation details of a class inside another class that is not exposed to the clients is an excellent way of reducing coupling. But there is a trade-off in performance as there is a pointer indirection to get to the actual implementation. If your non-functional quality attributes allow you to apply pImpl, definitely do. Our guide goes into additional details.
- Use header guards. Always. There is absolutely no reason why your headers are not kept under the header guards. If you think you cannot come up with proper names for the header guards, use #pragma once – where supported – and delegate that work to the compiler.
- Use better linkers. Just as compilers, linkers are getting better at their tasks too. Of the natively supported GNU binutils linkers, gold is faster than bfd. Using the llvm linker lld, is significantly faster than gold. Of course, gold supports more targets than lld. Choose wisely.
- Put some effort into maintaining your code. Removed unused code – be it blocks of code that are flagged by the compiler as unreachable or the files included for a long-removed functionality to the code that was intended purely for testing. When the compiler has lesser work to do it automatically reduces C++ compile time.
- Be wary of autogenerated code. There are scenarios where programs create source code of C++ based on some inputs. An external domain-specific language is a good example. The DSL may be text and it is converted to C++ and then compiled. This conversion to C++ might not be optimal. Check what code is generated and see if this can be optimized.
- Use cross-compilation when possible. Don’t insist on building Qt5 for Raspberry Pi natively. It will take overnight. There are better and well-documented ways to get desktop machines to cross-compile Qt for Raspberry Pi. Why insist on a native build if a Dockerfile is given? Spin up a docker container and get a painless build.
- Apply the YAGNI principle.
Don’t overload if you don’t need it now, don’t assume you will require it later. Don’t templatize a function or a class if you don’t need it. Keep things simple. Less is better and it will also reward you with faster builds.
Ok, Is There Something That I Shouldn’t Do?
Yes, we discussed some patterns to improve your build times. Now let us see some anti-patterns which we recommend you avoid.
- Don’t switch off compiler optimizations. Don’t do this. Compiler writers are the best in their game and the optimizations they do make your code faster and smaller. It is not worth the compilation time you save to disable them.
- Don’t remove comments in the code. Really? I am surprised you even considered removing code comments to make compilation times faster. Code documentation is there for a reason, don’t shoot yourself removing them to save compilation times.
- Don’t ditch best practices for saving compilation time. Yes, you know almost always auto (AAA) pattern is a best practice. (From C++17 it is always auto). To save some nanoseconds in the build, don’t go back to explicit typing, it is not worth the effort.
- Single Compilation Unit. Ah, the unity build. I strongly advise against this route, especially the maintenance nightmare it entails. We are in 2021 and let us modularize more. Developers coming after you will thank you for it.
- Don’t propose switching to a different language. You have heard Golang has fast compilations. What about Rust?
Don’t propose switching to a different language from C++ due to the bad compilation times. I have a better way for you!
We wrote this blog post and this guide trying to lay down everything you can (and can’t) do to reduce your C++ build times. However, most of the technical solutions require quite a bit of hard work and the gain is not always that substantial. If you’re searching for a dramatic acceleration of your C++ code while using your code (without changes) and your tools, check out our solution to reduce C++ compilation time.