Putting all my external code into an asmdef reduce my compile time by 75%
“30 second build times!” I told my cats. “What’s going on here.” Thus began my week long adventure to speed up my build times.
I’m not sure why Unity was taking 30 seconds then. Under test conditions my build took more like 8 seconds. In the end, I got it down to around 5 seconds. Was this worth it?
“I have a need for speed”
For normal C# projects, we can call MSBuild directly a get a Performance Summary for a build. Unfortunately, it seems Unity doesn’t use MSBuild (or at least I can’t figure out how to get Unity to output a MSBuild Performance Summary.) So instead, I scraped Unity editor logs to figure out what Unity does.
What Unity Does
Step one, Unity does a refresh. The Unity refresh looks for all the files that have changed, and makes a list of the projects that depend on those files. Most build systems keep track of time stamps and rebuild if the change time of the file is different. Unity being different, reads the entirety of every file that has changed to compute a hash. Unity will only build the file if the new hash is different. Even so, for a single file, this is really fast.
Step two, Unity does a compile. The Unity compile step calls the C# compiler to build the projects the Unity refresh determined need to be compiled. This is the step Asmdef files should help with. Everything not included in a folder covered by an asmdef file is built into the Assembly-CSharp or Assembly-CSharp.Editor assemblies. The docs say that they reference all asmdef files, but it practice it seems that only asmdef files with the "Auto Referenced" checkbox marked.
Step three, Unity does an update. The Unity update step puts the game and everything all back together again. Lots of stuff happens here, but Asmdef files shouldn’t impact this step too much.
Asmdef files to the rescue
Asmdef files allow us to break up our game up into smaller assemblies. We then manually configure the dependencies between the assemblies. How we do this will impact our compile times.
If we make our dependencies the code that doesn’t change much, this should speed up incremental builds.
Assembly-CSharp automatically takes dependencies on everything. Replacing this with our own asmdef with explicit dependencies might speed up incremental builds.
Spliting up independent code should speed up incremental builds as well.
Phase 0: Benchmark no asmdef files
We need a benchmark to compare to. This build has no asmdefs.
Phase 1: Put Unity asset store stuff into one asmdef
First I put all my Unity Asset store code that I never touch into WildDog.External, and WildDog.External.Editor. Not building this stuff on every change should speed things up.
Phase 2: Eliminate Assembly-CSharp
Next I put a file called WildDog.asmdef in my assets folder, and WildDog.Editor.asmdef in my Assets/Editor folder. With this change, Unity no longer creates an Assembly-CSharp assembly.
Phase 3: Split up independent code
Finally, I broke up the remaining code into more semi independent assemblies.
For testing purposes I did 30 incremental builds in each phase with one change to one of three files.
Total Build Time
The time I spend waiting for Unity to finish the build is what I really care about. We see that phase 1 has the most impact. Phase 2 and 3 seem to increase build time slightly. Probably due to additional assembly reference resolves. Lets look at the Unity build steps, ignoring the refresh step since it’s too fast to measure reliably with log file scraping at 100ms intervals.
Compile times are where we see an almost 75% improvement. With the other changes, we don’t see much difference. Again, more assemblies cause additional assembly reference resolves which slows things down a little bit.
Our updates phase gets a little slower. Again probably due to additional assembly reference resolves. The compile time improvements more than makes up for this.
Looking at our build time distributions, the impact of the additional assembly reference resolves doesn’t look like much. However, we still must be somewhat conservative with our use of asmdef files to create additional assemblies.
To speed up your Unity build times:
Benchmark your build times if you are trying to improve them
Compile all your source you never change into one assembly using an asmdef file
Limit the number of asmdef files you use
Limit dependendencies between asmdef files
Don’t put your most frequently changing code in an asmdef
If all else fails, reboot
Special note for Unity Asset Store publishers
Please include asmdef files for your asset. One for your runtime code, and one for your editor code.
If you don’t create asmdef files for your asset, please at least only have one editor folder. I need to put all the editor folders of all my plugins in the same place. When you have 30 different editor folders, I hurt.