Back to Devexpress

Reduce Application Launch Time

generalinformation-400286-build-deploy-reduce-application-launch-time.md

latest8.3 KB
Original Source

Reduce Application Launch Time

  • Oct 02, 2024
  • 6 minutes to read

When you build a .NET application, it is compiled into Microsoft Intermediate Language (MSIL). When a user launches the application, its MSIL code is compiled into machine code by the “just-in-time” (JIT) compiler. This process can cause noticeable delays. External DLLs (for example, DevExpress DLLs) may be loaded in addition to your own application, which means that any delay does not depend on the size of your code alone.

This topic describes how to reduce the WPF or WinForms application startup time if you have noticeable delays in your application due to the JIT compilation.

.NET Framework

Compile IL Code Into Native Code (Ngen)

Use the Native Image Generator (Ngen.exe) tool to compile assemblies’ IL code into native code. When a user runs your application, CLR loads the precompiled code from the native image cache so that no compilation is required at runtime. The Ngen.exe tool ships with the .NET Framework SDK.

You can compile your application’s IL code (and its dependencies’ IL code) into native code after the application installation or at the first application startup.

Compile Assemblies After the Application Installation

Create a script that executes the following code after a user installs your application:

cmd
C:\Windows\Microsoft.NET\Framework\v4.0.30319\ngen.exe install C:\MyApp.exe
cmd
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe install C:\MyApp.exe

Compile Assemblies at the First Application Startup

Add the following code to the App class for a WPF project or Program class for a WinForms project:

  1. Specify a path to the file that stores your executable’s hash. Create this file or load the saved hash if the file already exists:

  2. Obtain the hash for your executable. Cancel the operation if the application does not have changes:

  3. Obtain the path to ngen.exe :

  4. Create a process that runs ngen.exe :

  5. Run the process and save the executable’s hash:

Display the Complete Code Sample

csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
// ...

public partial class App : Application {
    public App() {
        var savedHash = string.Empty;
        var assemblyLocation = Assembly.GetEntryAssembly().Location;

        var hashPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "hash.txt");
        if (!File.Exists(hashPath)) {
            File.Create(hashPath);
        } else {
            savedHash = File.ReadAllText(hashPath);
        }

        var hash = string.Concat(SHA1.Create().ComputeHash(File.ReadAllBytes(assemblyLocation))
            .Select(x => x.ToString("x2")));
        if (hash.Equals(savedHash))
            return;

        var dotNetRuntimePath = RuntimeEnvironment.GetRuntimeDirectory();
        var ngenPath = Path.Combine(dotNetRuntimePath, "ngen.exe");

        var process = new Process {
            StartInfo = new ProcessStartInfo {
                FileName = ngenPath,
                Arguments = $"install \"{assemblyLocation}\" /nologo",
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden,
                UseShellExecute = true,
                Verb = "runas"
            }
        };

        try {
            process.Start();
            process.WaitForExit();
            File.WriteAllText(hashPath, hash);
        }
        catch {
            // ...
        }
    }
}
vb
Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Linq
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Security.Cryptography
' ...

Partial Public Class App
    Inherits Application

    Public Sub New()
        Dim savedHash = String.Empty
        Dim assemblyLocation = Assembly.GetEntryAssembly().Location

        Dim hashPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "hash.txt")
        If Not File.Exists(hashPath) Then
            File.Create(hashPath)
        Else
            savedHash = File.ReadAllText(hashPath)
        End If

        Dim hash = String.Concat(SHA1.Create().ComputeHash(File.ReadAllBytes(assemblyLocation)).Select(Function(x) x.ToString("x2")))
        If hash.Equals(savedHash) Then
            Return
        End If

        Dim dotNetRuntimePath = RuntimeEnvironment.GetRuntimeDirectory()
        Dim ngenPath = Path.Combine(dotNetRuntimePath, "ngen.exe")

        Dim process = New Process With {
            .StartInfo = New ProcessStartInfo With {
                .FileName = ngenPath,
                .Arguments = $"install ""{assemblyLocation}"" /nologo",
                .CreateNoWindow = True,
                .WindowStyle = ProcessWindowStyle.Hidden,
                .UseShellExecute = True,
                .Verb = "runas"
            }
        }
        Try
            process.Start()
            process.WaitForExit()
            File.WriteAllText(hashPath, hash)
        Catch
            ' ...
        End Try
    End Sub
End Class

Refer to the following article for information about various native image generation capabilities: Ngen.exe (Native Image Generator).

Use MultiCore JIT

In .NET Framework 4.5 and higher, you can use the System.Runtime.ProfileOptimization class to enable Multicore JIT. Multicore JIT uses parallelization to reduce the JIT compilation time during application startup.

Note

Using the MultiCore JIT is not as effective as compiling IL code into native code.

The code sample below demonstrates how to enable Multicore JIT in your application.

csharp
public App() {
    // Defines where to store JIT profiles
    ProfileOptimization.SetProfileRoot(@"C:\MyAppFolder");
    // Enables Multicore JIT with the specified profile
    ProfileOptimization.StartProfile("Startup.Profile");
}
vb
Public Sub New()
    ' Defines where to store JIT profiles
    ProfileOptimization.SetProfileRoot("C:\MyAppFolder")
    ' Enables Multicore JIT with the specified profile
    ProfileOptimization.StartProfile("Startup.Profile")
End Sub

The first time the application starts, the JIT compiler records every method it should compile. The CLR then saves a profile of the methods that were executed. The ProfileOptimization.SetProfileRoot method specifies an existing folder where the profiles are saved. Multicore JIT is not applied at this moment.

The second time the application runs, the ProfileOptimization.StartProfile method loads the specified profile from disk and uses this information to compile methods in the background before they are called from the main thread.

Refer to the following articles for more information:

.NET/.NET Core - Ready to Run Images

.NET/.NET Core allow you to reduce the application startup time by compiling your application to ReadyToRun (R2R) format.

R2R binaries contain native code similar to what the just-in-time (JIT) compiler produces. The native code is used to reduce the amount of work the JIT compiler needs to do as your application loads.

To publish an app in the R2R format, set the <PublishReadyToRun> option to true in your project file:

xml
<PropertyGroup>
  <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

The R2R binaries are larger, because they contain both the native and IL code.

Refer to the following topic for more information: Deploy .NET/.NET Core Applications.