Handmade Hero»Forums»Code
7 posts
Why use a unity build?

I’m aware of the basic concept of a unity build and how it’s supposed to increase compilation time by reducing the amount of files compiled by #including everything into one .c file. And I am aware that Casey follows this. However I’m still a beginner and I’m trying to learn about c and in the book I read it said that it is best practice for data abstractions to be separated by public interface (.h) and private implementation (.c) so my question is is there any other benefit to using a unity build, and is it worth using a unity build over organizing the file structure in the way that the book I read said?

Mārtiņš Možeiko
2415 posts / 2 projects
Why use a unity build?

That's the only goal of doing unity build - increased compilation speed.

There are some other advantages for optimized builds, for example, compiler can now inline across all functions which would not be possible if they are in different TU's. Although this is not a very important reason - as there are ways to do this in normal builds too, like inline functions or LTO.

The bigger disadvantage of unity builds is slower incremental builds. Sometimes if you include larger 3rd party libraries like stb image, or dear imgui, or sqlite and similar, then your incremental build times can go crazy high compared to building with separate TU's. In separate TU case those 3rd party source files will be built just once and .obj files reused when you're changing only your code.

149 posts
Why use a unity build?
Replying to mmozeiko (#26790)

Although this is not a very important reason - as there are ways to do this in normal builds too, like inline functions or LTO.

What did you mean by "inline functions"? You just said it's not possible to inline across all functions.

Also, What usually does a ".obj" file contain that makes it un-inlinable?

Mārtiņš Možeiko
2415 posts / 2 projects
Why use a unity build?
Edited by Mārtiņš Možeiko on
Replying to longtran2904 (#26795)

If you put functions in .h file as "inline" functions, then they can be inlined. Of course you should not do that for all functions otherwise there's no point of using multiple TU's. But for few selected functions that are important to performance you could put them in .h.

Inlining is part of compiler codegen process. It needs to perform many optimizations afterwards for inlining to make sense. Like constant folding, dead code elimination, etc.. But you cannot really do that on .obj file, as .obj file contains generated machine code (technically you could decompile it, inline and generate code again, but that would reduce how many optimizations you can apply because a lot of information about original code has been lost).

149 posts
Why use a unity build?
Replying to mmozeiko (#26796)

Does the compiler still generate .obj files for the "inline" functions?

Mārtiņš Možeiko
2415 posts / 2 projects
Why use a unity build?
Replying to longtran2904 (#26798)

Compiler generates obj file for every translation unit you compile. It puts everything from this translation unit into .obj file, including inline functions (as long as they are not static). Linker then is free to choose any only one copy from all of them to put into output file in case function was not inlined and is required for some calls. This is know as one definition rule.

149 posts
Why use a unity build?
Replying to mmozeiko (#26799)

Does a header file consider a .obj file? How does the "put all your "inline" functions in a .h file" example work?

Simon Anciaux
1241 posts
Why use a unity build?

Each translation units produce a .obj file. It means if you pass 3 files to the compiler (like cl main.c a.c b.c), it will output 3 obj file.

If you have some code that you want to be inlined, the translation units need to have access to the source code of the function to be able to have the instruction in each .obj files, it's not sufficient to only have the function prototype.

So if you want a function to be (considered to be) inlined in several translation units, in the file you include (.h or .c it doesn't matter) you need the full function body.

Also remember that the inline keyword is only a hint to the compiler and will most likely be ignored (I didn't include any in the examples).

Possible to inline:

/* possible_to_inline.h */
int function_to_inline( int x ) {
    int result = x * x;
    return x;
}

/* Translation unit 1 */
#include "possible_to_inline.h"
int main( void ) {
    /* ... */
    int result = function_to_inline( 3 );
    /* ... */
}

/* Translation unit 2 */
#include "possible_to_inline.h"
void tu_1( void ) {
    function_to_inline( 3 );
}

Impossible to inline

/* impossible_to_inline.h */
int function_to_inline( int x );

/* Translation unit 1 */
#include "impossible_to_inline.h"
int main( void ) {
    /* ... */
    int result = function_to_inline( 3 );
    /* ... */
}

/* Translation unit 2 */
#include "impossible_to_inline.h"

int function_to_inline( int x ) {
    int result = x * x;
    return x;
}

void tu_1( void ) {
    function_to_inline( 3 );
}
65 posts
Why use a unity build?
Edited by Shastic on

Another advantage to the unity build is simplicity. You don't have to learn complicated build systems.

The way I do unity build is I have one .h file with all my includes in it. Then I include that file in the .c file of whatever project I am working on. So:

#include "adsad1.h"
#include "adsad2.h"
#include "adsad3.h"
#include "adsad4.h"
#include "adsad5.h"

/* ****************************** */

/*
Put function prototypes here,
if there are multiple files depending on each other.
*/

/* ****************************** */

#include "adsad1.c"
#include "adsad2.c"
#include "adsad3.c"
#include "adsad4.c"
#include "adsad5.c"

I only put structs in the .h files and functions in the .c files. When I do it like that, I rarely have trouble with one function needing a struct or another function, but it is above them. In the rare case that happens, I use the commented area for function prototypes.

Something else you should be aware of, is if you use templates, this will slow your build down drastically, which means you will have to use a complicated build system. One of the reasons I use C instead of C++ is so I won't be tempted to use templates.