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" /> <Target Name="AfterBuild" DependsOnTargets="InstallConfiguration" />
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 Name="CompressJavascript" DependsOnTargets="_CreateScriptFolder;MergeJavascript"> <Exec Command="..\Bin\jsmin.exe < $(MergedJavascript) > $(CompressedJavascript)" /> </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 Name="CompressJavascript" Inputs="$(MergedJavascript)" Outputs="$(CompressedJavascript)" DependsOnTargets="_CreateScriptFolder;MergeJavascript"> <Exec Command="..\Bin\jsmin.exe < $(MergedJavascript) > $(CompressedJavascript)" /> </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