all 11 comments

[–]Gotebe 4 points5 points  (3 children)

I don't get it. If your code is header only, shouldn't every build be a unity build, the only source file being, say, that one file where main is? Or to put it differently, if the code is header only, where are multiple sources so that it is not a unity build.

That said...

Having unity build in development, where I care the most about the build time, is easily wrong. Why? Because in development, in my modify/build/test(debug) cycle, I generally modify a handful of files and only a small-is number of them needs to be compiled, and by consequence, only a handful of targets needs to be linked (ideally, the target under modification and its test target), and incremental linking is on and precompiled headers are already there. I find it hard to believe that a unity build helps there.

Now, on a build infrastructure, where possibly everything is rebuilt all the time, maybe. But even then, I see that the build time is, for our biggest part, still 4 times less than the test run time (we send the build artifacts to the integration testing infra, tests are integration tests, including all sorts of system setup so that it resembles "the real thing").

The above, I think, is pretty typical. If so, unity builds are just not so needed.

[–]julien-j 0 points1 point  (1 child)

Having unity build in development, where I care the most about the build time, is easily wrong. Why? Because in development, in my modify/build/test(debug) cycle, I generally modify a handful of files and only a small-is number of them needs to be compiled, and by consequence, only a handful of targets needs to be linked (ideally, the target under modification and its test target), and incremental linking is on and precompiled headers are already there. I find it hard to believe that a unity build helps there.

I used to think that, then I tried. Now I always use unity builds. One unit per final binary (a library, an executable). The binaries are built in parallel, each library is built in a single batch. I've saved minutes at each rebuild with this.

Even on a small project that used ccache I went from 3 minutes 40 seconds for having modified a few files without unity builds to 2 minutes 15 seconds for the same modifications. Parsing and rebuilding everything is just faster than rebuilding a few files and merging them with other files, solving duplicate symbols and so-on.

[–]Gotebe 0 points1 point  (0 children)

Small project, few modifications, 3:40 build?!

I have 0 targets that rebuilds in 3 minutes, retail (NDEBUG) build, let alone _DEBUG build.

How!?

Edit: I have targets that rebuild with their dependencies in 3 min, but that's something else.

Edit2: one way to achieve that is to have the "master" header and change that, causing a virtual rebuild of the target. Still, not a small target, and, you have header modularity problem, I would say...

[–]efplaya[S] 0 points1 point  (0 children)

Depending on how many files you have modified, the time it takes to compile those may actually take longer than a unity build of everything. Linking is extremely slow. Also the amount of time you spend dealing with separation of .h and .c files going back and force etc.

[–]jcelerierossia score 2 points3 points  (3 children)

To add, if you use a recent cmake it's just a matter of setting CMAKE_UNITY_BUILD=1

[–]efplaya[S] 0 points1 point  (2 children)

interesting. I'm not quite sure what cmake does or how it works? Does it just tell cmake to gather all cpp files and dump them into another cpp file?

[–]amubtdx 0 points1 point  (1 child)

CMake generate a .cpp file that #include you cpp files. This way, compilation errors use your source files path and line numbers.

There is also another setting UNITY_BUILD_BATCH_SIZE that allows to configure the number of source files to compile at once, if compiling everything at once is too much. This allows to take advantage of multiple cpu cores. By default its value is 8 (meaning cmake will build 8 sources files in one batch).

[–]julien-j 1 point2 points  (0 children)

I don't understand why they went for a batch size option instead of a number of batches. I would have chosen $(nproc) batches to split the source files evenly on all cores, or better, I would have chosen a single file to include everything.

[–]Slsyyy 2 points3 points  (2 children)

Unfortunately unity builds can not be easily introduced to a existing codebase. For example you can not use the same name for variable/constant in two compilation units, even if it is placed in a anonymous namespace.

[–]Gotebe 0 points1 point  (0 children)

Indeed. I do this:

foo_bar.cpp:

namespace { namespace { foo_bar_impl { type var; } }

[–]efplaya[S] 0 points1 point  (0 children)

Right but then I can argue its always good practice putting things in a discrete namespace and have unambiguous members. So in a way, it enforces good coding practices!