In this article, we will explore the concept of ahead-of-time (AOT) compilation in Blazor WebAssembly projects. We’ll explain how to enable AOT compilation, how it operates, and when it proves effective. Additionally, we’ll look at how it optimizes applications with CPU-intensive operations. Finally, we’ll discuss the potential benefits and drawbacks of using AOT.
Let’s dive in.
What Is Blazor WebAssembly Ahead-Of-Time (AOT) Compilation?
By default, Blazor WebAssembly applications run on a WebAssembly implementation of the Mono runtime. This runtime loads and executes the standard .NET assembly files that we get after compilation on the browser. It interprets these files using Just-In-Time (JIT) compilation support.
While this approach works perfectly for applications with simple tasks, it may exhibit slower performance when our application handles CPU-intensive tasks, such as complex mathematical operations. This can impact our app’s overall performance.
To address this performance issue, Blazor WebAssembly ahead-of-time (AOT) compilation was introduced in .NET 6. Blazor WebAssembly AOT compilation involves compiling the .NET runtime and our C# code directly to WebAssembly before browser execution.
With AOT compilation, the browser runs our app natively as WebAssembly. This compilation enhances our app’s runtime performance.
Furthermore, we perform Blazor WebAssembly AOT compilation only during our app’s publishing process. AOT is essentially a publish-time optimization. The publishing process, in which AOT compilation takes place, can be time-consuming. For this reason, we only perform it on the initial build of our app. In this article, we will publish our app to a folder.
Also, because AOT compilation takes a lot of time, we cannot use AOT when we run our app in a development environment.
Preparation of Blazor WebAssembly Project Without Ahead-of-Time (AOT) Compilation
Now, let’s demonstrate how ahead-of-time compilation can affect our application’s performance.
To start, let’s create a Blazor WebAssembly project using the Visual Studio project template. Alternatively, we can create a new blazor project:
dotnet new blazorwasm
In this Blazor app, we want to add a page that displays all the prime numbers between one and one million. This operation requires a lot of computation because it uses multiple nested loops with many iterations.
Creating a Prime Number Service for Our Blazor WebAssembly Project
First, let’s create the IPrimeNumberService
interface with a single method:
public interface IPrimeNumberService { Task<List<int>> GetPrimeNumbersAsync(); }
Now, let’s define a PrimeNumberService
class that implements the interface:
public class PrimeNumberService : IPrimeNumberService { public async Task<List<int>> GetPrimeNumbersAsync() { return await Task.Run(() => { var primes = new List<int>(); var isPrime = new bool[1000000 + 1]; for (int i = 2; i <= 1000000; i++) { isPrime[i] = true; } for (int p = 2; p * p <= 1000000; p++) { if (isPrime[p]) { for (int i = p * p; i <= 1000000; i += p) { isPrime[i] = false; } } } for (int i = 2; i <= 1000000; i++) { if (isPrime[i]) { primes.Add(i); } } return primes; }); } }
Here, we implement the GetPrimeNumbersAsync()
method. This method uses the Sieve of Eratosthenes algorithm to find, store in a list, and return asynchronously all the prime numbers within the range of one to one million.
For us to be able to inject this dependency into our page, we have to register it in our DI container in our Program.cs
class:
builder.Services.AddSingleton<IPrimeNumberService, PrimeNumberService>();
Creating a Prime Number Page for Our Blazor WebAssembly Project
Moving forward, we can now go ahead to create our PrimeNumber
page:
@page "/primenumbers" @inject IPrimeNumberService PrimeNumberService @using System.Diagnostics; @if (PrimeNumbers == null) { <p><em>Loading...</em></p> } else { <p>Time taken: @TimeToRun ms</p> <p>The Prime Numbers Between 1 and 1000000 are:</p> @foreach (var primeNumber in PrimeNumbers) { <p>@primeNumber</p> } } @code { private List<int>? _primeNumbers; public List<int>? PrimeNumbers => _primeNumbers; private double TimeToRun { get; set; } protected override async Task<List<int>> OnInitializedAsync() { var stopwatch = new Stopwatch(); stopwatch.Start(); _primeNumbers = await PrimeNumberService.GetPrimeNumbersAsync(); stopwatch.Stop(); TimeToRun = stopwatch.Elapsed.TotalMilliseconds; return _primeNumbers; } }
Here, we use the @page "/primenumbers"
directive to define the route for this component. Then, we inject our PrimeNumberService
class into it.
Upon loading this page, we trigger the OnInitializedAsync()
lifecycle method. In this method, we initialize an instance of the Stopwatch class from the System.Diagnostics
namespace. We use this instance to measure the amount of time, in milliseconds, it takes our GetPrimeNumbersAsync()
method to generate the list of prime numbers. Then, we store this duration in the TimeToRun
property.
Subsequently, on the page, if our PrimeNumbers
property is null
, we display 'Loading...'
. Otherwise, we display the time it took to generate the list of prime numbers and all the prime numbers.
With this, let’s now create a link to our PrimeNumber
page in the NavMenu.razor
file:
<div class="nav-item px-3"> <NavLink class="nav-link" href="primenumbers"> <span class="oi oi-sort-ascending" aria-hidden="true"></span> Prime Numbers </NavLink> </div>
Alright, with the app ready, we can now publish and execute it to measure the time it takes to execute.
Performance of Our Blazor WebAssembly App Without AOT Compilation
First, let’s publish our app to a folder directly from Visual Studio. We can also publish our app by executing:
dotnet publish -c Release
It’s important to note that we have to start up a web server to host our published Blazor WebAssembly app. Therefore, once the publishing process is complete, let’s navigate to the wwwroot
folder within the publish directory, and from there, let’s open a terminal.
In the terminal, let’s install the dotnet serve tool globally:
dotnet tool install -g dotnet-serve
After that, let’s execute the dotnet serve
command.
This command launches the server and hosts our application locally. Now, we can visit the URL and open the PrimeNumber
page from the navigation menu:
From the result, we can see that it takes our application 167.1ms to generate the prime numbers. Loading the page two more times takes our application 154.9 ms and 142.6 ms respectively.
We can see that our app takes an average of 154.86 ms to generate the prime numbers.
How to Setup Ahead-of-Time (AOT) Compilation in Blazor WebAssembly
Now, let’s explore how we can enhance our app’s performance by reducing the time it takes to generate these prime numbers through the use of ahead-of-time compilation (AOT).
To set up AOT compilation, first, we have to install the Blazor WebAssembly build tools. To do this, we have to launch the Visual Studio Installer and modify our current Visual Studio app by checking the .NET WebAssembly Build Tools box.
Alternatively, we can use our terminal with administrator privilege to install the build tools:
dotnet workload install wasm-tools
Next, let’s enable AOT in our project file:
<PropertyGroup> <RunAOTCompilation>true</RunAOTCompilation> </PropertyGroup>
Here, we set the RunAOTCompilation
tag to true. With this, when we publish this application to a particular target environment, we will AOT compile it to WebAssembly.
We should note that in the GitHub repo, we have commented out this instruction in our csproj file. We do this to enable the project to pass the Jenkins build on our remote repository. Therefore, when we use this solution locally, we should ensure to uncomment these lines.
Checking the Performance of Our App With AOT
Now, when we publish our application again and run it three times, we get setup times of 43 ms, 61.5 ms, and 42.1 ms respectively. This results in an average setup time of 48.87 ms.
From these results, we can see that our application now generates prime numbers significantly faster. We highly welcome such performance improvement, especially in applications where we execute more complex algorithms.
Advantages of Blazor WebAssembly Ahead-of-Time (AOT) Compilation
Now, let’s talk about the perks of Blazor WebAssembly AOT compilation. AOT enables our Blazor WebAssembly app to run as efficiently as possible.
Firstly, ahead-of-time compilation improves the performance of our application by ensuring faster startup times and enhanced runtime performance. This happens because, with AOT, we compile our code during publishing, making it ready for immediate execution by the browser.
Also, by eliminating the interpretation of our app’s code, ahead-of-time compilation makes our application more secure. The reason for this is that it reduces the room for attacks during runtime.
Lastly, during AOT compilation, we optimize our application for a specific environment by eliminating unnecessary code.
Considering these benefits, we can conclude that ahead-of-time compilation can significantly enhance the overall user experience.
Disadvantages of Blazor WebAssembly Ahead-of-Time (AOT) Compilation
Although ahead-of-time compilation can speed up our applications, it comes with some disadvantages.
Firstly, Blazor WebAssembly AOT compilation increases build time as it transforms all our C# code into Wasm code. This extension of the application’s build and deployment process time can be a major drawback.
Furthermore, the size of our executable file grows larger than when we do not perform ahead-of-time compilation. This happens because now, the outcome of compilation is larger.
Conclusion
In this article, we have covered the processes involved in setting up, publishing, and running a Blazor WebAssembly application with AOT compilation enabled. We also observed the performance enhancements it provides and discussed its advantages and disadvantages. From the results, we can conclude that there is still significant room for improvement in Blazor WebAssembly ahead-of-time compilation.
Ultimately, when deciding whether to use Blazor WebAssemby AOT compilation, we must consider our application’s specific requirements. There are situations where the benefits may outweigh the drawbacks, making it suitable for our app. And there are also cases where the drawbacks could make AOT compilation less suitable for our needs.