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