[HN Gopher] Speed Up C++ Compilation
       ___________________________________________________________________
        
       Speed Up C++ Compilation
        
       Author : todsacerdoti
       Score  : 80 points
       Date   : 2023-08-05 18:12 UTC (4 hours ago)
        
 (HTM) web link (devtalk.blender.org)
 (TXT) w3m dump (devtalk.blender.org)
        
       | ligurio wrote:
       | I've collected the most popular methods for speeding up
       | compilation and testing in https://github.com/ligurio/sqa-
       | wiki/wiki/speedup-your-ci
        
       | gavinray wrote:
       | C++ Modules should hopefully significantly improve this situation
        
         | flatline wrote:
         | I see no reason to believe this will actually be the case based
         | on anecdotal evidence from folks I know who have tried out the
         | early module implementations. I'm hoping they are wrong, but I
         | do not think that modules or anything else is going to result
         | in a significant speedup for the average case, and large C++
         | compile times will remain a perpetual feature of the language.
        
           | pjmlp wrote:
           | As per Microsoft talks, C++23 _import std;_ is faster than a
           | plain _#include <iostream>_.
        
             | bluGill wrote:
             | But clang and GCC have not reported anything (it doesn't
             | work), but they have always been careful to not get
             | people's hopes up
        
       | MarkSweep wrote:
       | > Multiple Git Checkouts
       | 
       | You don't need to do a full clone to have multiple working copies
       | of your repo checked out. You can use the git worktree command to
       | create these working copies:
       | 
       | https://git-scm.com/docs/git-worktree
        
         | billyzs wrote:
         | I use worktrees, but some builds which uses git hash will fail
         | in worktree checkouts, thinking that "it's not a git directory"
         | (forgot the exact error, it's been a while); haven't found a
         | solution to this
        
           | dragly wrote:
           | One way to get an error when retrieving a git hash is by
           | building inside a Docker container. If you mount the root
           | directory of a work tree, but not the "git-common-dir", git
           | will fail to give you the commit hash.
        
         | higherhalf wrote:
         | They mention git worktrees in the next paragraph.
        
         | derriz wrote:
         | What is the advantage of worktrees over multiple clones? Using
         | multiple clones is trivial - there are no new git concepts to
         | learn - and works perfectly with all build tools particularly
         | ones that heavily use caching. What productivity improvements
         | do worktrees bring?
        
           | anyfoo wrote:
           | I work with massive repositories, and creating new worktrees
           | works in an instance, because it only creates hardlinks
           | instead of copying files. Saves disk space, too.
           | 
           | Also, all branches stay in sync between worktrees. For
           | example, I do git fetch on one worktree, and remote branches
           | are up to date on all of them.
        
           | senkora wrote:
           | Significantly less disk usage is the main one for me. If you
           | have a workflow where each task has a worktree, and you have
           | 10-15 tasks in progress, and the repo is a decades old
           | monorepo, then storing only one copy of the history is a big
           | savings.
           | 
           | A task could be any small thing that requires its own
           | checkout for some possibly stupid reason, like running the
           | regression tester or temporarily running an old version of a
           | binary out of your dev checkout over NFS if there was a bad
           | deploy.
        
             | tynorf wrote:
             | FWIW, local git clones use hard links for object files, so
             | share a lot of their data.
             | 
             | https://www.git-scm.com/docs/git-clone#Documentation/git-
             | clo...
        
           | IshKebab wrote:
           | Branches are shared between worktrees. It's a significant
           | benefit.
           | 
           | The only downside is that they don't work with submodules, if
           | you are unfortunate enough to be using submodules.
        
           | andromeduck wrote:
           | Probably makes it a bit easier to propagate changes between
           | copies
        
             | derriz wrote:
             | Moving work/commits from one local clone to another is
             | something I do regularly - just using vanilla git
             | push/fetch.
             | 
             | I'll grant that saving disk space is a concern for many -
             | not for me - the largest repo I use is 2-3 million LOC with
             | about 10 or 15 years history and the 4-5GB per clone
             | doesn't bother me.
        
               | IshKebab wrote:
               | > just using vanilla git push/fetch
               | 
               | Right but that's quite a lot more faff than not having to
               | do anything at all.
        
             | dragly wrote:
             | Yes, exactly. Git worktrees will for instance make it
             | easier to merge changes between multiple checked out
             | branches without pushing upstream.
             | 
             | For big repositories with long histories (like Qt),
             | worktrees also save some disk space.
        
       | nice__two wrote:
       | I invite everyone to have a look at compiling LibreOffice [0].
       | 
       | Doing a full compile, has been known to bring even the most
       | powerful machines to their knees.
       | 
       | They've employed a lot of the referenced article's suggestions
       | _and it still_ is a massive code-base to compile.
       | 
       | [0]:
       | https://wiki.documentfoundation.org/Development/BuildingOnLi...
        
         | bravetraveler wrote:
         | If on Fedora, you can give it a whirl like so:
         | cd $(mktemp -d)         fedpkg clone -a -b f38 libreoffice &&
         | cd libreoffice         fedpkg mockbuild
         | 
         | This requires the _' fedpkg'_ and _' mock'_ packages be
         | installed, and that your user is in the _' mock'_ group
         | 
         | Compiling is one of many benchmarks I do, lol
         | 
         | Edit: note for posterity -- the branch will change as time goes
         | by. Fedora 38 is the current release, so _f38_ is used.
         | 
         | The source for the code and the target for the build don't have
         | to match. Look into different _' mock roots'_ at _/
         | etc/mock/*.cfg_
        
       | speps wrote:
       | Some people might not know this but Epic actually did a lot of
       | this automatically within Unreal Engine by writing their own
       | equivalent of CMake. The whole codebase is going through
       | UnrealBuildTool which auto generates headers as well. It calls
       | compilers, linkers, etc. for each supported platform.
       | 
       | It allows them to enable the "unity build" stuff automatically
       | for example, and on a per file basis as well. If you have a file
       | checked out locally (in Perforce terms), it won't include it into
       | the usual unity compilation unit for it and will compile it on
       | its own as it's likely it's one you're going to edit a lot if
       | it's one you've got checked out.
       | 
       | Anyway, I thought it was an interesting datapoint as I think it's
       | quite unique. To the level of Boost using a modified fork of Jam
       | at some point...
        
       | ladberg wrote:
       | Would recommend Build Bench to help gain a mental model for how
       | different styles of code impact build times: https://build-
       | bench.com/
        
       | gauddasa wrote:
       | If it's too long to read then you can safely skip to "C++20
       | Modules" section near the end of the article.
        
         | GuB-42 wrote:
         | Assuming you are using C++20, which as of now, few codebases
         | use.
         | 
         | As work, now, we mostly use C++11 and C++14, some projects are
         | C++17, but I am not aware of a single C++20 project, I think
         | all active projects that are C++03 or below have been migrated
         | to something more recent.
         | 
         | Just a data point.
        
           | Snafuh wrote:
           | Unreal Engine actually switched to C++20 with their next
           | release (preview available).
           | 
           | I was quite surprised to read this in their roadmap, as
           | Unreal's codebase is quite massive and also uses some custom
           | build tools.
        
       | hashtag-til wrote:
       | Any good guide on how to apply some of these tips on cmake?
        
         | rewmie wrote:
         | CMake supports unity builds right out of the box.
         | 
         | https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.htm...
         | 
         | And support for ccache is not an option to some build systems.
         | For instance, Visual Studio projects in general and the msvc
         | compiler require a lot of fighting to get it to work.
         | 
         | Also, ccache is renowned for not supporting precompiled
         | headers.
        
           | ninepoints wrote:
           | I wrote a comment in the linked thread as to why I really
           | dislike Unity builds. Some folks make it work for them, and
           | they have some attractive/tempting properties, but I think
           | the siren's call of unity builds should be utterly rejected
           | whenever possible.
        
             | rewmie wrote:
             | I agree, in particular by the way unity builds can break
             | code that relies on internal linkage to avoid conflicts
             | between symbols. But it's nevertheless a technique that's
             | supported by cmake and can be enabled by flipping a flag.
        
               | Conscat wrote:
               | At my job, it speeds up clean builds 3x on the CI server,
               | but a portion of those gains are lost to code breaking
               | frequently due to unity.
        
               | ninepoints wrote:
               | Yea, honestly these types of numbers really aren't
               | surprising, but usually when you profile a build and dig
               | into why the build perf was so bad to begin with, you
               | generally find stuff like template abuse, bad header
               | organization, and countless other untold sins that were
               | better off fixed anyways.
        
               | fpoling wrote:
               | Chromium once supported unity builds. For me it speeded
               | up builds from 6 hours down to 1 hour 20 minutes on a
               | Windows laptop. And Chromium tries to make their headers
               | reasonable.
               | 
               | Chromium eventually dropped support for such builds. At
               | Google the developers have access to a compilation farm
               | that compiles within like 5 minutes. With such farm unity
               | builds makes things slower as they decrease parallelism.
               | So Google decided not to support them not to deal with
               | very occasional compilation breakage.
        
               | rewmie wrote:
               | Bad header organization is perhaps the gravest yet
               | unspoken cardinal sin of both C and C++ development.
               | 
               | I lost count of the number of times that I had to deal
               | with cyclic dependencies accidentally introduced by
               | someone when they started to add #include without any
               | criteria.
        
       | ndesaulniers wrote:
       | Build times of the Linux kernel with clang are dominated by the
       | front end. 30 years of crufty headers is a lot to parse.
       | 
       | What we actually need is tooling to help recommend how to
       | refactor or split headers in a way to reduce having to parse a
       | ton of dead code.
       | 
       | Or even better, tooling that would generate one off headers per
       | TU. I think it could be done.
        
       | rewmie wrote:
       | The article mentions forward declararions but does not mention
       | the age-old pimpl idiom.
       | 
       | https://en.cppreference.com/w/cpp/language/pimpl
       | 
       | Pimpl greatly reduce build times by removing includes from the
       | interface header at the expense of requiring pointer
       | dereferencing, and it's a tried and true technique.
       | 
       | Another old school technique which still has some impact in build
       | times is to stash build artifacts in RAM drives.
        
         | jb1991 wrote:
         | Pimpl has so many other drawbacks, though.
        
           | rewmie wrote:
           | Which ones?
        
             | [deleted]
        
             | jb1991 wrote:
             | There are discussions in many places about this, including
             | right here on HN. I encourage you to Google it. Here are
             | some examples:
             | 
             | https://news.ycombinator.com/item?id=24537267
             | 
             | https://www.cppstories.com/2018/01/pimpl/#pros-and-cons
             | 
             | It used to be a widely advertised technique maybe a decade
             | ago, but it has long gone out of style.
        
               | rewmie wrote:
               | I feel you're just adding noise to an otherwise
               | interesting discussion.
               | 
               | If all you have to add is handwave over "Pimpl has so
               | many other drawbacks" and to reply "google them" when
               | asked to substantiate your baseless claim, I feel it was
               | preferable if you sat this discussion out.
               | 
               | The noise you add is particularly silly as all your
               | references point is the pointer dereferencing drawback I
               | already pointed out and you claimed there were more.
        
               | vvanders wrote:
               | Heap fragmentation is another, pimpl works but it's
               | really papering over limitations in the pre-processor and
               | header model that leaks details to downstream consumers.
        
               | rewmie wrote:
               | > Heap fragmentation is another
               | 
               | For the rare cases where potential pimpl users care about
               | heap allocations, there's a pimpl variant called fast
               | pimpl with replaces the heap allocation with a opaque
               | member variable that holds memory to initialize the impl
               | object. Since C++11 the opaque object can be declared
               | with std::aligned_storage.
        
               | andreidd wrote:
               | std::aligned_storage has been deprecated since C++23
        
               | jb1991 wrote:
               | > all your references point is the pointer dereferencing
               | drawback I already pointed out
               | 
               | I think maybe you didn't read through both the items I
               | linked or the many others that come from a simple google
               | query. One of my links for example points out the memory
               | fragmentation issues, which can also affect performance,
               | as another commenter here has also pointed out. There's
               | more to the story than pointer de-referencing or memory
               | context -- many drawbacks worth knowing about.
               | 
               | There is nothing baseless here; there are pros and cons.
               | But it's not in good form to ask people for details that
               | are easily looked up on a topic as well-known as this
               | one. We are not a reference source for you.
        
           | BenFrantzDale wrote:
           | I'm curious: do you use a `const std::unique_ptr<Impl>` or
           | just a `std::unique_ptr<T>` or do you have a template that
           | provides value semantics for the `Impl`? If I used PImpl a
           | lot I'd make a `value<T>` that encapsulates ownership with
           | value semantics.
        
             | BenFrantzDale wrote:
             | And conversely, if you are using classical polymorphism,
             | you can get essentially the effect of PImpl by having an
             | abstract base class with a static factory function that
             | returns a unique pointer, then implement that factory
             | function by in the cpp file having a concrete class that is
             | defined there and returned as a `std::unique_ptr<IBase>`.
             | That gives you separation of API from implementation
             | without a memory indirection, but you then can't have it as
             | a value type.
        
         | [deleted]
        
         | wizofaus wrote:
         | It strikes me as something of a language flaw that without
         | pimpl any addition/removal/ modification of private member
         | functions, or even renaming of private member variables
         | triggers full recompilation of every source file that needs to
         | use a particular class. I understand that changes to the _size_
         | of the object should do so (even if it 's just new private
         | member variables), but if it can be determined by the rules of
         | the language that a particular change to a class definition
         | can't possibly affect other compilation units that happen to
         | use that class, then why can't the compiler automatically
         | determine no recompilation is necessary e.g. by calculating and
         | storing a hash of everything about a class that can potentially
         | have an external effect and comparing that value rather than
         | relying on timestamps...
        
           | rewmie wrote:
           | > strikes me as something of a language flaw that without
           | pimpl any addition/removal/ modification of private member
           | functions, or even renaming of private member variables
           | triggers full recompilation of every source file that needs
           | to use a particular class.
           | 
           | It is not a language flaw. C++ requires types to be complete
           | when defining them because it needs to have access to their
           | internal structure and layout to be in a position to apply
           | all the optimizations that C++ is renowned for. Knowing this,
           | at most it's a design tradeoff, and one where C++ came out
           | winning.
           | 
           | For the rare cases where these irrelevant details are
           | relevant, C++ also offers workarounds. The pimpl family of
           | techniques is one of them, and type erasure techniques are
           | also useful, and protocol classes with final implementations
           | are clearly another one. Nevertheless, the fact that these
           | techniques are far from being widely used demonstrates that
           | there is zero need to implement them at the core language
           | level.
        
           | BenFrantzDale wrote:
           | If your internal representation is stable, you can put
           | private functions in a private friend class that is only
           | defined in the cpp file: `private struct access; friend
           | struct access;`.
        
         | fpoling wrote:
         | The primary reason for Pimpl is to ensure binary compatibility
         | for C++. QT uses it extensively precisely for this reason.
         | Reduced compilation time is just a nice bonus.
        
         | Blackthorn wrote:
         | I'll be glad if I never have to see PIMPL ever again. It makes
         | tracking down the actual implementation of some functionality
         | so much harder than it has to be.
        
         | TillE wrote:
         | Pimpl is basically my default C++ style, with exceptions made
         | when the teeny tiny bit of overhead might actually matter.
         | 
         | It just feels great to be able to totally refactor the
         | implementation without touching the header.
        
       | jheriko wrote:
       | how about we stop the module and linker nonsense and just do it
       | right?
       | 
       | stick everything in a single compilation unit automagically and
       | have a flag to let bad legacy code that reuses global symbols
       | produce errors/warnings so they are easy to fix, or be ignored so
       | that we can still have backwards compatibility.
       | 
       | that is just the "hacky" way of doing things and it indeed works
       | wonderfully, although often breaking any kind of incremental
       | build - this is because we implement incremental build wrong,
       | which is suitably easy to fix as well.
        
         | mostlylurks wrote:
         | C++ compilation times can become quite slow even when you're
         | doing what you describe manually (known as a "unity build", and
         | not particularly rare in some niches), even if you avoid
         | including the same headers multiple times. Of course, a lot of
         | this depends on what features you use; template-heavy code is
         | going to be slower to compile than something that's more-or-
         | less just C fed into a C++ compiler.
        
         | LoganDark wrote:
         | I used to spend lots and lots of time finding and working on
         | header-only libraries that didn't have other dependencies or
         | weird linking requirements - you'd just `#include` them, and
         | that was the code, and you could use it just like that. But in
         | large projects, this starts to get a bit unwieldy and the whole
         | "every file is its own unit, and can depend / be depended on by
         | any other units without issue" thing is actually super useful.
        
       ___________________________________________________________________
       (page generated 2023-08-05 23:00 UTC)