Benchmarking .NET MAUI Startup After CoreCLR Migration

One of the first things consumers notice about a mobile application is its beginning.

If users spend several seconds staring at a splash screen before the first interactive panel displays, a feature-rich program might have great functionality but yet seem sluggish.

Startup for.NET MAUI apps involves more than just loading the initial page:

For .NET MAUI applications, startup includes more than loading the first page:

Application Launch
      ↓
Operating System
      ↓
Runtime Initialization
      ↓
.NET MAUI Initialization
      ↓
Dependency Injection
      ↓
Application Initialization
      ↓
First Page Creation
      ↓
First Interactive UI

The move to CoreCLR for .NET MAUI mobile applications makes startup benchmarking particularly relevant. In .NET 11 Preview 6, CoreCLR became the only runtime for .NET MAUI applications targeting Android, iOS, and Mac Catalyst. Microsoft also identifies performance and application size among the areas expected to benefit from the unified runtime direction.

However, an important distinction is required:

A runtime change does not automatically mean that every application will start faster.

Startup depends on the runtime, application initialization, dependencies, native libraries, networking, database initialization, reflection, configuration, device hardware, and build configuration.

Therefore, the correct engineering approach is to establish a controlled baseline and measure the complete startup path.

What Should Be Measured?

A useful startup benchmark should not produce only one number.

Measure at least:

Metric Why It Matters
Cold startup Measures launch from a terminated state
Warm startup Measures resume/relaunch behavior
Time to first UI Represents perceived responsiveness
Time to interactive UI Represents usable application state
Memory after startup Shows initial memory footprint
Peak startup memory Detects initialization spikes
CPU during startup Shows initialization pressure
Application size Measures deployment footprint
Crash/hang rate Detects migration problems

The exact values should be collected from the target application.

Do not use theoretical startup numbers as production benchmarks.

Define Startup Before Measuring It

The first mistake in startup benchmarking is failing to define what “startup” means.

Consider these possible endpoints:

A. Process started
B. Runtime initialized
C. MAUI initialized
D. Main page created
E. First frame rendered
F. First interactive frame
G. Initial data loaded

These are different measurements.

For most user-facing applications, a useful primary metric is:

Cold startup =
application launch
→ first interactive application screen

You may also track:

Startup to first frame
Startup to interactive UI
Startup to initial data loaded

Keep the definition identical between runtime versions.

Establish a Baseline Before Migration

Suppose an application currently runs using the previous runtime configuration.

Record:

Runtime
.NET SDK
MAUI version
Device
Operating system
Build configuration
Linking/trimming settings
Application version

Then record the measurements:

Measurement Baseline
Cold startup Measure
Warm startup Measure
First interactive UI Measure
Memory after startup Measure
Peak memory Measure
CPU Measure
Package size Measure

These numbers become the reference point.

After migrating to CoreCLR, repeat exactly the same test.

Keep the Test Environment Consistent

A meaningful runtime comparison should look like:

Same application
        +
Same dependencies
        +
Same device
        +
Same OS version
        +
Same configuration
        +
Same startup workflow
        +
Different runtime

Changing several variables simultaneously makes the result difficult to interpret.

For example, avoid comparing:

Old runtime
+ old application
+ old dependencies

versus

CoreCLR
+ redesigned startup
+ new libraries
+ new database initialization

If startup changes, there is no reliable way to determine which change caused it.

Test Physical Devices

Mobile startup performance should be measured on physical devices.

Emulators are useful for development and automated testing, but they do not necessarily represent the performance characteristics of a user’s device.

Create a representative device matrix:

Low-end device
Mid-range device
High-end device

The exact devices should reflect the application’s supported-device policy.

Record the device model and operating-system version with every benchmark.

Test Cold Startup

A cold-start test begins with the application terminated.

A simplified process is:

Application terminated
        ↓
Launch
        ↓
Runtime initialization
        ↓
MAUI initialization
        ↓
Application initialization
        ↓
First interactive UI

Repeat the measurement several times.

For example:

Run 1
Run 2
Run 3
Run 4
Run 5

Do not rely on the first run alone.

Startup can vary because of device state, OS activity, storage behavior, and other background processes.

Test Warm Startup Separately

Warm startup is a different workload.

For example:

Application running
        ↓
Background
        ↓
Resume

or:

Application closed
        ↓
Launch again

depending on how the application lifecycle is being evaluated.

Record warm-start measurements separately from cold-start measurements.

Do not combine them into one average.

Measure First Interactive UI

A splash screen does not necessarily mean that the application is ready.

For example:

Splash Screen
      ↓
Loading
      ↓
Main Page Created
      ↓
API Call
      ↓
Data Loaded

If the application waits for the API call before becoming interactive, users may perceive the entire process as startup.

A better architecture can sometimes allow:

Launch
 ↓
Interactive Shell
 ↓
Background data loading
 ↓
Content appears

This distinction matters when evaluating startup improvements.

The benchmark should therefore distinguish:

Time to interactive UI

from:

Time to fully populated UI

Instrument Application Initialization

Application code often contributes more startup work than expected.

Consider:

public static MauiApp CreateMauiApp()
{
    var builder =
        MauiApp.CreateBuilder();

    builder
        .UseMauiApp<App>();

    builder.Services.AddSingleton<AuthService>();
    builder.Services.AddSingleton<ApiService>();
    builder.Services.AddSingleton<DatabaseService>();

    return builder.Build();
}

Dependency registration itself is not necessarily expensive.

The problem often appears when initialization performs real work:

var database =
    InitializeDatabase();

var configuration =
    LoadConfiguration();

var settings =
    LoadSettings();

If these operations execute synchronously during startup, they become part of the critical path.

Measure them separately.

Add Startup Timing

A simple timing mechanism can identify application-level startup costs:

var stopwatch = Stopwatch.StartNew();

var app = MauiProgram.CreateMauiApp();

Debug.WriteLine(
    $"MAUI initialization: {stopwatch.ElapsedMilliseconds} ms");

Then instrument major initialization stages:

Debug.WriteLine(
    $"Database initialization: {stopwatch.ElapsedMilliseconds} ms");

Debug.WriteLine(
    $"Configuration loading: {stopwatch.ElapsedMilliseconds} ms");

Debug.WriteLine(
    $"Main page creation: {stopwatch.ElapsedMilliseconds} ms");
For production diagnostics, use a structured telemetry system rather than relying on

The important principle is to identify where the startup time is actually being spent.

Separate Runtime Time From Application Time

Suppose the measurement changes from:

Baseline: 1.8 seconds
CoreCLR: 1.6 seconds

That does not tell you why.

The startup path might look like:

Runtime              400 ms
MAUI                  300 ms
Application           500 ms
Database              200 ms
UI                    400 ms

After migration:

Runtime              350 ms
MAUI                  300 ms
Application           500 ms
Database              200 ms
UI                    400 ms

The application-level difference may be entirely runtime-related.

But if:

Runtime              350 ms
Application           700 ms

then the overall startup could still become slower.

This is why instrumentation is more valuable than a single stopwatch measurement.

Avoid Network Calls in the Critical Startup Path

One of the most common startup problems is synchronous dependence on remote services.

For example:

var profile =
    await api.GetProfileAsync();

If this operation blocks the first interactive screen, startup now depends on:

Network
Server
Authentication
DNS
TLS
API latency

A more responsive architecture can often display the initial UI and load non-critical data afterward.

For example:

await ShowInitialUiAsync();

_ = LoadNonCriticalDataAsync();

The exact design depends on whether the data is required before the application can safely operate.

Do not move critical initialization into the background merely to make a benchmark number smaller.

Measure Database Initialization

Local databases can also affect startup.

For example:

var database =
    new SQLiteConnection(databasePath);

database.CreateTable<Customer>();

Measure:

Database creation
Schema verification
Migration
Connection initialization
Initial query
A database migration that runs during startup can dominate the runtime initialization cost.

Test both:

Fresh installation
Existing installation
The latter is particularly important because real users do not always start from a completely empty database.

Test First Installation Separately

A fresh installation can involve:

Package installation
First launch
Permission requests
Database creation
Default configuration
Authentication
Initial synchronization

An existing installation may involve:

Existing database
Existing preferences
Existing authentication state
Migration

These are separate startup scenarios.

Create at least two benchmark categories:

Scenario Description
Fresh install First launch after installation
Existing install Launch with existing application state

Do not use one scenario as a substitute for the other.

Measure Memory at Startup

Startup memory should be measured at consistent points.

For example:

Process launch
     ↓
First UI
     ↓
Initial data
     ↓
Startup complete

Record:

Working set
Managed memory where available
Native memory where available
Peak memory
The exact metrics depend on the platform and diagnostic tooling.

The goal is to identify:

Unexpected memory increase

rather than focusing on one platform-independent number.

Test Memory After Repeated Launches

A single launch does not reveal memory-retention problems.

Repeat:

Launch
Use application
Background
Resume
Close
Launch again

Then compare memory behavior.

For an application that remains active for long periods, also test:

Navigate
Open details
Load images
Return
Repeat

If memory continually grows, investigate retained objects, event handlers, native resources, caches, and page lifetimes.

Do not automatically classify every memory difference as a runtime regression.

Test UI Initialization

XAML-heavy pages can create substantial startup or navigation work.

Consider:

<VerticalStackLayout>
    <Label Text="{Binding Title}" />
    <CollectionView ItemsSource="{Binding Items}" />
    <Image Source="{Binding Image}" />
</VerticalStackLayout>

The cost can come from:

XAML loading
Binding creation
Handler creation
Image loading
Collection initialization
Layout
Rendering

Measure the first page separately from later navigation.

A runtime migration may change the timing of one stage without changing the overall architecture.

Avoid Loading Large Data Sets During Startup

For example:

var products =
    await database.Products
        .ToListAsync();

If the database contains thousands of records, loading everything during startup can delay the first screen.

Prefer loading only what is required:

var products =
    await database.Products
        .OrderBy(p => p.Name)
        .Take(50)
        .ToListAsync();

Then load additional data based on user interaction.

This is an application optimization rather than a CoreCLR-specific optimization, but it is important when measuring runtime migration effects.

Measure Image Initialization

Images can significantly affect perceived startup.

Avoid unnecessarily loading large assets before the first screen becomes interactive.

For example:

Startup
 ↓
Small placeholder
 ↓
Interactive UI
 ↓
Image loaded asynchronously
If the application requires a specific image before becoming usable, include it in the benchmark.

The test should reflect the actual product experience rather than an artificially optimized startup path.

Test Dependency Injection Resolution

Dependency injection can become expensive if large object graphs are created immediately.

For example:

builder.Services.AddSingleton<
    HeavyService>();

builder.Services.AddSingleton<
    AnalyticsService>();

builder.Services.AddSingleton<
    DatabaseService>();

Registration does not necessarily instantiate everything.

However, resolving a service may create a dependency graph:

MainPage
  ↓
ViewModel
  ↓
Service
  ↓
Database
  ↓
Repository
  ↓
Other Services
Measure the time required to create the actual startup object graph.

Avoid optimizing registrations simply because the application has many services.

Measure first.

Test Reflection and Runtime Discovery

Applications sometimes perform assembly scanning during startup:

var types =
    Assembly.GetExecutingAssembly()
        .GetTypes();

This can increase initialization work.

Search for:

Assembly.GetTypes()
Type.GetType()
Activator.CreateInstance()
reflection-based registration
plugin discovery
serializer discovery
If CoreCLR migration changes startup behavior, reflection-heavy initialization is worth profiling.

Do not remove reflection blindly.

Determine whether it is actually part of the startup bottleneck.

Test Release Builds

Startup benchmarks must eventually use the application’s real deployment configuration.

Compare:

Debug
Release

and, where applicable:

Trimmed
AOT

configurations.

A debug build is not representative of the production binary.

Native AOT and trimming can change application behavior and impose restrictions on dynamic runtime features, so applications using those deployment modes need dedicated validation.

Compare Application Size

Application size is another useful migration metric.

Record:

APK
AAB
IPA
Installed size

where applicable.

Keep packaging configuration constant.

For example:

Baseline
   ↓
Same assets
   ↓
Same dependencies
   ↓
Same trimming configuration
   ↓
Measure

CoreCLR
   ↓
Same assets
   ↓
Same dependencies
   ↓
Same configuration
   ↓
Measure

Otherwise, a package-size difference may be caused by unrelated dependency changes.

Benchmark Startup on Multiple Architectures

Where the application supports different device architectures, test the deployment variants used in production.

For example:

ARM64
Other supported architecture

The exact architecture matrix depends on the target platform and distribution requirements.

Record architecture with every benchmark.

Run Repeated Measurements

Startup performance is noisy.

Run each scenario multiple times.

For example:

Scenario A
Run 1
Run 2
Run 3
Run 4
Run 5
Run 6
Run 7

Then report a distribution rather than a single measurement.

A useful table is:

Runtime p50 p95 Minimum Maximum
Baseline Measure Measure Measure Measure
CoreCLR Measure Measure Measure Measure

The exact statistical method can be adapted to the testing environment.

The important point is to capture variability.

Define a Regression Threshold

Before interpreting results, define what qualifies as a meaningful regression.

For example:

Potential startup regression:
>10% increase in median startup

Potential memory regression:
>10% increase in startup memory

These are example engineering thresholds, not Microsoft requirements.

Your organization should select thresholds based on:

  • Existing SLOs
  • User expectations
  • Measurement variance
  • Device class
  • Application criticality

Avoid declaring a regression because one run differs by a few milliseconds.

Compare p50 and Tail Behavior

If startup measurements vary significantly, the average alone can hide problems.

Suppose:

Run 1 → 1.4 sec
Run 2 → 1.5 sec
Run 3 → 1.5 sec
Run 4 → 1.6 sec
Run 5 → 3.8 sec

The outlier matters.

For user-facing applications, investigate why a small number of launches take significantly longer.

Possible causes include:

Storage
OS background activity
Network
Initialization
Garbage collection
Resource loading

Do not automatically attribute the outlier to the runtime.

Create a Startup Benchmark Harness

A useful benchmark harness should capture:

Runtime version
Application version
Device
OS
Build configuration
Scenario
Run number
Startup timestamp
First UI timestamp
Interactive timestamp
Memory
CPU

Store the result in a machine-readable format.

For example:

{
  "runtime": ".NET 11",
  "platform": "Android",
  "build": "Release",
  "scenario": "ColdStart",
  "run": 1,
  "startupMs": 0,
  "interactiveMs": 0
}

Replace the zero values with actual measurements from the test environment.

This allows results to be compared over time.

Build a Startup Regression Dashboard

For teams maintaining multiple mobile releases, track:

Startup
Memory
Package size
Crash rate

over each runtime upgrade.

For example:

Version Cold Startup Memory Package Size Crashes
Baseline Measure Measure Measure Measure
Candidate Measure Measure Measure Measure

The goal is not to create a vanity dashboard.

The dashboard should identify whether a release crosses an agreed regression threshold.

Separate Runtime Regressions From Application Regressions

Suppose startup increases after migration.

Use this investigation sequence:

Startup Regression
       ↓
Reproduce
       ↓
Same application?
       ↓
Same dependencies?
       ↓
Same device?
       ↓
Same configuration?
       ↓
Profile startup
       ↓
Identify changed stage
       ↓
Create minimal reproduction

If the problem disappears after removing application initialization, it may be application-specific.

If a minimal application reproduces the difference, the runtime or framework becomes a stronger candidate.

Create a Minimal MAUI Startup Reproduction

A useful reproduction can be extremely small:

MauiProgram
   ↓
App
   ↓
MainPage

Avoid:

Database
Authentication
Analytics
Networking
Large dependencies

unless they are required to reproduce the problem.

Then compare the same minimal application under the relevant runtime versions.

This helps determine whether the startup difference is:

CoreCLR
.NET MAUI
Native platform
Application
Dependency

Test Authentication Separately

Authentication often introduces network and browser dependencies.

Measure:

Startup without authentication
Startup with existing session
Startup requiring authentication

This distinction is important.

For example:

Cold startup
     ↓
Existing token
     ↓
Local validation

is very different from:

Cold startup
     ↓
Network request
     ↓
Authentication server
     ↓
Token refresh

Do not mix them into a single startup benchmark.

Test Offline Startup

A production mobile application should often remain usable when the network is unavailable.

Test:

Network available
Network unavailable
Slow network
Intermittent network

If the application waits for a network call before rendering the first screen, startup can become highly variable.

This is a valuable test regardless of runtime version.

Test Startup After Upgrade

A runtime migration should also test an existing installation.

For example:

Previous application
      ↓
Install upgraded build
      ↓
First launch
      ↓
Migration
      ↓
Startup

The first startup after an application upgrade may perform:

  • Database migration
  • Preference migration
  • Cache rebuilding
  • Authentication validation
  • File migration

Measure this separately from ordinary cold startup.

Common Startup Problems

Everything Is Initialized in App

Large constructors or startup methods can create a long critical path.

Review:

public App()
{
    InitializeComponent();

    // Avoid large synchronous initialization here.
}

Move non-critical work out of the critical startup path where the application architecture allows it.

Database Migration Runs During Startup

Database migrations can be expensive.

Measure migration time separately.

Network Calls Block the First Screen

Do not make non-critical remote calls prerequisites for initial interactivity.

Large Object Graphs Are Created Immediately

Review startup dependency resolution and lazy initialization opportunities.

Large Images Load Before UI

Defer non-critical media where appropriate.

Analytics Initialization Blocks Startup

Measure third-party initialization independently.

If a vendor SDK blocks startup, determine whether initialization can be deferred without affecting required functionality.

Troubleshooting

CoreCLR Build Starts More Slowly

First determine whether the increase comes from:

Runtime
MAUI
Application
Dependency
Database
Network

Use startup instrumentation rather than assuming the runtime is responsible.

Startup Improved but Memory Increased

Treat this as a trade-off requiring investigation.

Measure:

Startup
Peak memory
Steady-state memory

A faster startup with substantially higher memory may not be an overall improvement for low-memory devices.

Startup Is Stable on Desktop but Slow on Mobile

Desktop results are not representative of mobile startup.

Use physical mobile devices for the final benchmark.

Only Release Build Shows a Problem

Inspect:

Trimming
AOT
Reflection
Native dependencies
Packaging

The release configuration may exercise code paths that Debug does not.

Only One Device Class Regresses

Investigate device-specific characteristics:

CPU
Memory
Storage
OS version
Architecture

A runtime behavior may interact differently with different hardware.

Recommended Benchmark Matrix

A serious CoreCLR startup evaluation can use this matrix:

Dimension Scenarios
Runtime Baseline / CoreCLR
Platform Android / iOS / Mac Catalyst
Device Low / Mid / High
Build Debug / Release
Installation Fresh / Existing
Startup Cold / Warm
Network Online / Offline / Slow
Authentication Existing session / Login required
Data Empty / Existing / Large
Metrics Startup / Memory / CPU / Size

Not every application needs every scenario.

The matrix should reflect the application’s architecture and supported platforms.

Best Practices

  1. Define startup precisely before benchmarking.
  2. Establish a baseline before migration.
  3. Use the same device and OS for before/after comparisons.
  4. Test physical devices.
  5. Measure cold and warm startup separately.
  6. Measure time to first interactive UI.
  7. Instrument application initialization.
  8. Separate network and database initialization from runtime startup.
  9. Measure memory alongside startup.
  10. Test fresh and existing installations.
  11. Test Debug and Release separately.
  12. Test trimming/AOT configurations used for production.
  13. Run multiple iterations.
  14. Compare distributions rather than one run.
  15. Define regression thresholds before interpreting results.
  16. Test Android, iOS, and Mac Catalyst independently.
  17. Record SDK, runtime, MAUI, device, OS, and build configuration.
  18. Create a minimal reproduction for confirmed runtime-level issues.

Frequently Asked Questions
Does CoreCLR guarantee faster .NET MAUI startup?

No.

Microsoft identifies performance and application-size improvements as expected benefits of the unified CoreCLR direction, but actual application startup depends on the complete application and deployment environment.

What is the most important startup metric?

For a user-facing application, time to first interactive UI is usually more meaningful than process-start time alone.

However, track several metrics because a single number cannot explain why startup changed.

Should I benchmark an emulator?

Emulators are useful for development and automated testing, but final performance comparisons should include representative physical devices.

Should startup include API calls?

Only if the application intentionally requires those calls before becoming usable.

Measure network-dependent initialization separately from runtime and local startup.

How many benchmark runs should I perform?

There is no universal number.

Run enough repetitions to characterize normal variability and investigate outliers. The exact number depends on the device, test harness, and required confidence.

Should I compare Debug and Release builds?

Yes, but separately.

Release should be the primary production performance benchmark.

Does application size affect startup?

Potentially.

Larger packages can affect installation and deployment characteristics, but package size alone does not determine startup time. Measure both independently.

What should I do if startup gets slower after migration?

First reproduce the result.

Then profile the startup path and determine which stage changed:

Runtime
MAUI
Application
Dependency
Database
Network
Native integration

Create a minimal reproduction if the issue appears runtime-related.

Conclusion

The CoreCLR transition in .NET MAUI creates an opportunity to evaluate mobile startup behavior with better measurement discipline.

Microsoft’s .NET 11 documentation describes performance and application size among the areas expected to benefit from the unified runtime direction, while CoreCLR is now the runtime path for MAUI mobile applications targeting Android, iOS, and Mac Catalyst.

But the correct engineering question is not:

“Is CoreCLR faster?”

It is:

“How does CoreCLR change startup behavior for this application under a controlled workload?”

A reliable benchmark follows this process:

Baseline
   ↓
Define Startup
   ↓
Control Environment
   ↓
Measure Cold Start
   ↓
Measure Warm Start
   ↓
Measure Memory
   ↓
Profile Initialization
   ↓
Test Release Build
   ↓
Test Multiple Devices
   ↓
Compare Results
   ↓
Investigate Regressions

The most important distinction is between runtime startup and application startup.

A .NET MAUI application may spend significant time initializing databases, resolving services, loading configuration, creating pages, loading images, authenticating users, or contacting APIs. Those operations can dominate the user-visible startup experience even when the underlying runtime is performing well.

Therefore, a credible CoreCLR migration benchmark should measure the complete startup path while keeping the application, device, dependencies, and configuration controlled.

Only then can a team determine whether a startup change is actually caused by the runtime migration or by another part of the application.

Best and Most Recommended ASP.NET Core 10.0 Hosting

Fortunately, there are a number of dependable and recommended web hosts available that can help you gain control of your website’s performance and improve your ASP.NET Core 10.0 web ranking. HostForLIFE.eu is highly recommended. In Europe, HostForLIFE.eu is the most popular option for first-time web hosts searching for an affordable plan. Their standard price begins at only €3.49 per month. Customers are permitted to choose quarterly and annual plans based on their preferences. HostForLIFE.eu guarantees “No Hidden Fees” and an industry-leading ’30 Days Cash Back’ policy. Customers who terminate their service within the first thirty days are eligible for a full refund.

By providing reseller hosting accounts, HostForLIFE.eu also gives its consumers the chance to generate income. You can purchase their reseller hosting account, host an unlimited number of websites on it, and even sell some of your hosting space to others. This is one of the most effective methods for making money online. They will take care of all your customers’ hosting needs, so you do not need to fret about hosting-related matters.