[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)