To link, or not to link, that is the question
Should I use Link Framework SDKs Only or Don't Link? This topic that comes up every now and then and peoples opinions vary a little bit with regards to it. This topic was brought up again with a recent blog post by James Montemagno titled Investing Time in the Xamarin Linker for Smaller App Sizes.
The particular tweet that got me curious was this tweet from Brandon.
I think I get what Brandon is talking about. Using the linker during development so if there are any bugs of it stripping out things that it shouldn't you will find them during development instead of when testing your final release build of the app. I somewhat agree with this. Linker issues are annoying to find, diagnose, and fix. I've come across issues in a video editing app I work on where the linker would rip out key parts of AVFoundation. In our case this caused video recording events to randomly never fire. Because we were only getting this in our release build we couldn't use the debugger to help find the issue (I'll write more about that issue in another post). However using the linker adds something else to consider, build times.
For the linker to do what it needs to do (remove code that isn't called resulting in a smaller file size) it's going to have to do some work, that work takes time. Your app may now be smaller to copy over to your device/simulator/emulator and you may catch a linker issue earlier in your development, but at what cost?
As one of my lecturers at university once said "The great thing about computer science is that we'll never die wondering, what if?.
Test Method
I grabbed the largest Xamarin.Forms project I have (closed source video recording/editing app) and did some tests. The reason I used a Xamarin.Forms app is because I wanted to test the linker behaviour in both a iOS and an Android apps. Using a Forms app will also let us have a peek at if there is any linker performance differences between iOS and Android app building.
The test case is to be repeated for iOS device, iOS simulator, Android device, Android emulator. This is my method of testing:
1. Clean app.
2. Delete bin/obj directory.
3. Restore nuget packages.
4. Right mouse click on native platform project, select build, and record the build times from the build output pad.
5. Modify Forms project* and repeat step 3-4 three more times to average out the values.
* technically our compile time now includes building of the Forms project and isn't a direct linker to no linker comparison. I felt this was acceptable as I wanted to give real world performance numbers.
My test machine is a 2018 MacMini with a 3.2ghz 6-core i7 processor and 32gb of RAM.
Results
A direct link to all of my results (including some not mentioned here) can be found here. Please let me know if any of my math in there is wrong.
Here are the key takeaways I have when looking over the data:
- When we don't link compile times decreased across the board.
- At minimum we reduced our compile time by 37%.
- When we don't link the file size increased on iOS (as expected), but it didn't seem to change the file size on Android.
There are some odd anomalies in my data. Have a look at the Android compile time dropping from 60 seconds to 20 seconds between some of the build times. I repeated this a few times and I confirmed that the Forms project and the Android project are both building. I don't know where that extra 40 seconds went 🤔
So should I set my debug builds to not use the linker?
Maybe? It all depends. 30 seconds saved with every iOS device build. How many times a day to you compile and deploy that app to your device? 10, 100, 200? That could be saving you 5 minutes, 50 minutes, an hour and 40 minutes (probably not this much) every day. Is that wasted time? That all depends on what you do while your code is compiling. Your 30 second Facebook break may turn into a 3 minute Facebook break. Or you might use that time more productively to only read 30 seconds worth about when I made a Raspberry Pi read swearing git commits at me.
My results above are specific to my app on my machine with my development workflow. You may have more code and less resources, or less code and more resources. It's possible that specific things you are doing will cause you to run into a linker issue more often than me. You may have a slower HDD, or a faster SSD where you build your code from. Maybe you even have a 32-core Xeon hackint0sh which handles linking faster (or does it? Is linking a single threaded or multithreaded operation?).
Do your own tests to make up your own mind about your own app on your own machine. As I quoted earlier, "The great thing about computer science is that we'll never die wondering, what if?".
BONUS: Using the intermediate designer file
Another tweet that caught my attention his week was this one by Dan.
The Resource.designer.cs file drives me crazy, always wanting to be committed into my git repo after minor resource changes. This whole "generate it my obj folder" thing is news to me. To make this change you will need to add the following to your Android csproj:
<PropertyGroup>
<AndroidUseIntermediateDesignerFile>true</AndroidUseIntermediateDesignerFile>
</PropertyGroup>
And then remove anything relating to your Resrouce.designer.cs which in my case was:
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
...and...
<Compile Include="Resources\Resource.Designer.cs" />
So on one hand I no longer have to deal with Resource.Designer.cs anymore, but on the other hand this file will no need to be generated at compile time. Will generating this ruin all the build time we saved by disabling the linker?
Nope. Building for Android emulator, not using the linker, my initial compile time was 57.72 sec. The build after that was then 19.17 sec, faster than any of my other recorded compile times! So should we all rush to our csproj and enable AndroidUseIntermediateDesignerFile to reduce build times?
Nah. This test and my previous tests were ran on seperate days. There was a few code changes between the two days, and this compile time difference is probably also within the margin of error. BUT you should enable it because it'll save you time for never having to commit its changes to your git repository ever again!