A while back, I posted about a Powershell script I wrote to help analyze build performance. Today I spent a good amount of time looking at the output and tweaking things. I came across a couple BIG performance enhancements that I thought I would share.
Tip #1 - Don’t start external processes
Starting another process is a time consuming activity, and if you are doing it on a post/pre build event, that is going to slow your build down tremendously. I was looking through my project, and found this post build event.
<propertygroup>
<postbuildevent>"$(MSBuildBinPath)\msbuild.exe" "$(SolutionDir)ApplyConfiguration.msbuild" /p:Configuration=$(ConfigurationName)</postbuildevent>
</propertygroup>
In the diagnostic output, I saw the _Exec _ call took 649ms.
Improving performance is simple, just import the MSBuild file and call the target directly. The refactored build looks like the following.
<import project="$(SolutionDir)ApplyConfiguration.msbuild"></import>
<target dependsontargets="InstallConfiguration" name="AfterBuild"></target>
And the MSBuild timing is pretty amazing, 5ms. That’s half a second in savings.
Tip #2 - Understand Inputs/Outputs
In MSBuild, targets can have a list of input and output files. Before building the target, it checks to make sure there is a need to build based on the timestamps of the inputs and outputs, also known as building incrementally.
Lets look at an example target for compressing Javascript.
<target dependsontargets="_CreateScriptFolder;MergeJavascript" name="CompressJavascript">
<exec command="..\Bin\jsmin.exe < $(MergedJavascript) > $(CompressedJavascript)"></exec>
</target>
This executes in about 104 ms. Not a long time, but if you are doing it over and over, it could end up adding up quickly. Refatoring this to execute quickly is extremely simple.
<target dependsontargets="_CreateScriptFolder;MergeJavascript" inputs="$(MergedJavascript)" name="CompressJavascript" outputs="$(CompressedJavascript)">
<exec command="..\Bin\jsmin.exe < $(MergedJavascript) > $(CompressedJavascript)"></exec>
</target>
The first build, it takes the same time. But all subsequent builds execute in 0ms if the $(MergedJavascript) file hasn’t changed.
Tip #3 - Reduce dependencies
After you have optimized everything else, you will see a couple targets that take the majority of your build time, ResolveAssemblyReferences and ResolveProjectReferences. Unfortunately, there isn’t a quick fix for this one. It comes back to your application architecture and dependency management.
Even compiling isn’t that big a deal, since compiling is done incrementally if files have changed. Dependencies have to be resolved each build though.
78 ms CoreCompile 1 calls
149 ms ResolveProjectReferences 1 calls
188 ms ResolveAssemblyReferences 1 calls