{"id":3266,"date":"2026-07-05T22:21:07","date_gmt":"2026-07-05T22:21:07","guid":{"rendered":"https:\/\/timallanwheeler.com\/blog\/?p=3266"},"modified":"2026-07-06T01:44:47","modified_gmt":"2026-07-06T01:44:47","slug":"into-depth-unity-build","status":"publish","type":"post","link":"https:\/\/timallanwheeler.com\/blog\/2026\/07\/05\/into-depth-unity-build\/","title":{"rendered":"Into Depth &#8211; Unity Builds"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">My Into Depth project is still fairly small, in the grand scheme of things. I&#8217;ve authored about 17k lines of code, which you&#8217;d think would compile pretty quickly, but I was seeing clean compile times of about 40 seconds. That is too long.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sure, I&#8217;m not just compiling the code I wrote. <code>glad<\/code>, <code>glm<\/code>, <code>imgui<\/code>, and <code>stb<\/code> increase the line count by about an order of magnitude to 120k. However, 40s is 40s, and I wanted to see what I could do to reduce it.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Problems<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">There are fundamentally two problems here &#8211; long full compilation times and long incremental compilation times. A full compilation happens after, e.g., <code>make clean<\/code>, where everything is compiled fresh. This naturally takes longer, since all object files have to be generated. An incremental compilation is what happens most of the time, where you have an existing set of object files and just need to recompile one or two of them and re-link.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">The Compilation Process<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">To understand how we&#8217;re going to speed things up, its helpful to understand what, roughly, is happening in the first place.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Preprocessor<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The first stage, before any compiling happens, is where the preprocessor runs over each <code>.cpp<\/code> file and specifically handles # commands. The preprocessor doesn&#8217;t actually understand C++ syntax, but is more of a glorified find and replace tool.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It finds every <code>#include<\/code> and copy-pastes the contents of that header file into the <code>.cpp<\/code> file.<\/li>\n\n\n\n<li>It finds every <code>#define<\/code>, i.e., macro, and replaces it with the corresponding text.<\/li>\n\n\n\n<li>It strips out all comments.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The result is a massive, single file of pure C++ code called a <em>translation unit<\/em>. For a standard codebase where your C++ file has many inputs, the translation unit can be pretty large.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"540\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/Compilation-2.jpg\" alt=\"\" class=\"wp-image-3270\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/Compilation-2.jpg 960w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/Compilation-2-300x169.jpg 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/Compilation-2-768x432.jpg 768w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/figure>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\">The Compiler<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The compiler translates the C++ code in each translation unit into raw machine code &#8211; the binary instructions that your specific CPU understands &#8211; as an <em>object file.<\/em> On Linux\/Mac, these are <code>.o<\/code> files, and on Windows these are <code>.obj<\/code> files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Object files contain:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A header<\/li>\n\n\n\n<li>a code segment with the binary instructions for the functions defined in the translation unit<\/li>\n\n\n\n<li>a data segment with initialized static variables <\/li>\n\n\n\n<li>a read-only data segment with initialized static constants<\/li>\n\n\n\n<li>a section for uninitialized static data (e.g., <code>int stuffs[1000000];<\/code>)<\/li>\n\n\n\n<li>A <em>symbol table<\/em> listing the functions and variables that this translation unit can offer to others.<\/li>\n\n\n\n<li>A set of unresolved references that the object file expects to exist but doesn&#8217;t have just yet &#8211; functions or global variables it needs from other object files.<\/li>\n\n\n\n<li>Debugging information.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"912\" height=\"341\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/2026-06-28_14-13.png\" alt=\"\" class=\"wp-image-3273\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/2026-06-28_14-13.png 912w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/2026-06-28_14-13-300x112.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/06\/2026-06-28_14-13-768x287.png 768w\" sizes=\"auto, (max-width: 912px) 100vw, 912px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Concepts like structs only matter during the compilation phase, where they inform the compiler how much memory to allocate on the stack or what offsets to look at variables for. The resulting object files only care about memory addresses, function blocks, and global variables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Linker<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Once all of the <code>.cpp<\/code> files have been compiled into a bunch of <code>.obj<\/code> files, the <em>linker<\/em> will handle the task of resolving references. If an object file needs a reference that the linker cannot find, it will issue the <code>undefined reference<\/code> or <code>unresolved external symbol<\/code> errors we know so well. Similarly if it finds multiple object files offering the same function signatures, it will output a <code>multiple definitions<\/code> error.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When building a executable (as opposed to a library), the linker starts by creating a new, empty file on disk (e.g.,<code> game.exe<\/code>). This file has an OS-specific format, such as ELF on Linux and PE on Windows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next, the linker reads every <code>.obj<\/code> file and copies the machine code binary into the executable, stacking them into one large, contiguous section.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Each object file listed its unresolved symbols, along with where, in its compiled code block, the address of the symbol should be written once the symbol is finally resolved. The linker thus works through the copied binary sections and writes the appropriate jump instructions to jump to the right function calls or load the right global variable address. This process is called <em>relocation<\/em>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"262\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-37.png\" alt=\"\" class=\"wp-image-3276\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-37.png 840w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-37-300x94.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-37-768x240.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">The linking step also handles any libraries that your executable depends on. Any <em>static library<\/em> (<code>.a<\/code> for &#8216;archive&#8217; file on Linux, <code>.lib<\/code> on Windows) is basically a collection of object files, and is copied into your executable like the other objects. Any <em>shared library<\/em> (<code>.so<\/code> file on Linux, <code>.dll<\/code> for &#8216;dynamic linked library&#8217; on Windows) is <em>not<\/em> copied, but the linker instead records a reference to it in the executable that the <em>dynamic linker <\/em> connects when your OS goes to execute your file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once the linker has completed relocation, it is done, and <code>game.exe<\/code> is a self-contained block of machine code (except for references to dynamic libraries). You can actually delete all of the <code>.obj<\/code> files and the executable will work just fine. The primary reason <code>.obj<\/code> files are kept around is for incremental builds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Faster Clean Builds<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we have an understanding of the compilation process, we can reason about changes that will speed up compile time. The first such speedup will be for <em>clean builds<\/em>, which is a complete build that runs the entire compilation pipeline without using any cached work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Under a typical build structure, many object files are produced and then linked:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"889\" height=\"431\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-52.png\" alt=\"\" class=\"wp-image-3281\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-52.png 889w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-52-300x145.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-52-768x372.png 768w\" sizes=\"auto, (max-width: 889px) 100vw, 889px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The trick here is the <em>unity build<\/em>, which collapses the project&#8217;s many object files into just one:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"936\" height=\"436\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-56.png\" alt=\"\" class=\"wp-image-3282\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-56.png 936w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-56-300x140.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-04_13-56-768x358.png 768w\" sizes=\"auto, (max-width: 936px) 100vw, 936px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is achieved by creating a <code>.cpp<\/code> file that includes all of the project&#8217;s other <code>.cpp<\/code> files, and then only have our Makefile build that one target.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In practice, the linker still needs to do some work. Most code bases will still link against libraries, and it is recommended to compile external dependencies that we do have the source for into separate <code>.obj<\/code> files and link against them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Time is saved in the linking phase by not having to stitch together potentially thousands of small object files. All of the memory math is handled by the compiler, which has access to all of the definitions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The primary time savings typically comes from eliminating redundant header parsing. In compilation, each translation unit receives copies of the headers it includes. The same header, such as <code>&lt;vector><\/code>, may be used in many translation units, and will be repeatedly parsed for each one. Having a single translation unit allows the <code>#pragma once<\/code> guards to do their work and ensure the preprocessor only copies each header once, so each header is only parsed once.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Unity builds also benefit from being able to do more inlining, as methods across translation units either cannot be inlined, or require link-time optimization to be enabled, which makes linking that much slower. In a unity build, everything is in one object file, so the entire program can be optimized.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The final speedup comes from reduced disk I\/O. The compiler does not need to repeatedly read header files or write many object files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Moving to a unity build dropped clean build times for ~45s to under 5s!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Faster Incremental Builds<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">During typical development, code is rebuilt incrementally. Here, a full build has already happened, you edit one or two <code>.cpp<\/code> files, and run <code>make<\/code> again. In an incremental build, only the <code>.obj<\/code> files for the two edited <code>.cpp<\/code> files need to be recompiled, followed by linking.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using a unity build means changing a single <code>.cpp <\/code>file requires the compiler to do the full work of producing the one <code>.obj<\/code> file all over again. As such, large projects will tend to use standard builds for typical development and unity builds for release builds or CI\/CD pipelines.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can use another technique to speed up incremental builds. Here, we aim to reduce the time it takes to compile individual object files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Time can be saved by reducing the work done by the preprocessor in copying header files included in other header files by using <em>forward declarations<\/em> instead.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consider the following example, where a <code>Weapon<\/code> struct is defined in <code>weapon.h<\/code> and it is used by a <code>Player<\/code> struct in <code>player.h<\/code>: <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"888\" height=\"216\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-24.png\" alt=\"\" class=\"wp-image-3284\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-24.png 888w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-24-300x73.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-24-768x187.png 768w\" sizes=\"auto, (max-width: 888px) 100vw, 888px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If we later change <code>Weapon<\/code>, such as by adding a new <code>int cooldown<\/code>, and go to compile, the build system will see that <code>weapon.h<\/code> has changed. The <code>player.h<\/code> file includes <code>weapon.h<\/code>, so it is marked for recompilation and any file that includes <code>player.h<\/code> is also recompiled. A simple field change has cascaded into a large number of updated object files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As written, <code>player.h<\/code> must include <code>weapon.h<\/code>, because <code>Player<\/code> contains a <code>Weapon<\/code>, and the compiler needs to know its size. We can avoid this dependency by using a pointer instead. The compiler cares about memory layouts, and pointers are all the same size, so we can use a <em>forward declaration<\/em> to promise the compiler that <code>Weapon<\/code> exists somewhere and avoid including <code>weapon.h<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"879\" height=\"210\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-38.png\" alt=\"\" class=\"wp-image-3285\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-38.png 879w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-38-300x72.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-38-768x183.png 768w\" sizes=\"auto, (max-width: 879px) 100vw, 879px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, if <code>Weapon<\/code> is changed, <code>player.h<\/code> is completely unaffected. Any <code>.cpp<\/code> files that merely include <code>player.h<\/code> no longer need to be recompiled.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same trick works for methods as well. Passing a struct like <code>Weapon<\/code> by value will require knowing its size. Changing to a pointer or reference (which is basically a pointer) removes that requirement, and a forward declaration is enough to promise that the type will eventually be defined.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"896\" height=\"262\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-52.png\" alt=\"\" class=\"wp-image-3286\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-52.png 896w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-52-300x88.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/07\/2026-07-05_14-52-768x225.png 768w\" sizes=\"auto, (max-width: 896px) 100vw, 896px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">The Best of Both Worlds<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">We can get the benefits of both unity builds during full recompilation and forward declarations during incremental builds by modifying our <code>Makefile<\/code> to run a default incremental build when <code>make<\/code> is involved and have a separate command, <code>make unity<\/code>, that compiles <code>unity.cpp<\/code> directly into an executable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># ---------------------------------------------------------<br># TARGET 1: The Incremental Build (Default)<br># ---------------------------------------------------------<br>all: $(TARGET)<br><br>$(TARGET): $(OBJS)<br>\t$(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS)<br><br>%.o: %.cpp<br>\t$(CXX) $(CFLAGS) -c $&lt; -o $@<br><br># ---------------------------------------------------------<br># TARGET 2: The Unity Build<br># ---------------------------------------------------------<br># Notice this target does NOT depend on $(OBJS). It bypasses them.<br>unity: src\/unity.cpp<br>\t$(CXX) $(CFLAGS) src\/unity.cpp -o $(TARGET) $(LDFLAGS)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This sort of a setup is not quite a panacea &#8211; you must suffer the cost of a full non-unity build in order to take advantage of incremental builds. As such, I&#8217;m personally going with all unity build, all the time for now (<a href=\"https:\/\/hero.handmade.network\/forums\/code-discussion\/t\/263-file_organization__unity_build\">something Casey Muratori has espoused<\/a>). Clean files seem safest.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is worth mentioning that unity builds also prevent repeated definitions using the same function name. Any nameless namespaces or <code>static<\/code> helper functions in your <code>.cpp<\/code> files all get merged together, hence a chance for collision. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Overall, I found this foray into compilation time quite satisfying. Not only did this process reduce how much time I sit around waiting for code to recompile, it also forced me to better understand the whole C++ compilation process. Oftentimes, a basic understanding of the system can make a huge difference in making informed decisions and understanding what the compiler is trying to tell you when something goes wrong.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>My Into Depth project is still fairly small, in the grand scheme of things. I&#8217;ve authored about 17k lines of code, which you&#8217;d think would compile pretty quickly, but I was seeing clean compile times of about 40 seconds. That is too long. Sure, I&#8217;m not just compiling the code I wrote. glad, glm, imgui, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-3266","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3266","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/comments?post=3266"}],"version-history":[{"count":10,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3266\/revisions"}],"predecessor-version":[{"id":3290,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3266\/revisions\/3290"}],"wp:attachment":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/media?parent=3266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/categories?post=3266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/tags?post=3266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}