{"id":55620,"date":"2025-02-20T10:05:00","date_gmt":"2025-02-20T18:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=55620"},"modified":"2025-02-20T06:42:53","modified_gmt":"2025-02-20T14:42:53","slug":"dotnet-9-performance-improvements-in-dotnet-maui","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-9-performance-improvements-in-dotnet-maui\/","title":{"rendered":".NET MAUI Performance Features in .NET 9"},"content":{"rendered":"<p>.NET Multi-platform App UI (.NET MAUI) continues to evolve with each\nrelease, and .NET 9 brings a focus on trimming and a new supported\nruntime: NativeAOT. These features can help you reduce application\nsize, improve startup times, and ensure your applications run smoothly\non various platforms. Both developers looking to optimize their .NET\nMAUI applications and NuGet package authors are able to take advantage\nof these features in .NET 9.<\/p>\n<p>We&#8217;ll also walk through the options available to you as a developer\nfor measuring the performance of your .NET MAUI applications. Both CPU\nsampling and memory snapshots are available via <code>dotnet-trace<\/code> and\n<code>dotnet-gcdump<\/code> respectively. These can give insights into performance\nproblems in your application, NuGet packages, or even something we\nshould look into for .NET MAUI.<\/p>\n<h2>Background<\/h2>\n<p>By default, .NET MAUI applications on iOS and Android use the\nfollowing settings:<\/p>\n<ul>\n<li>&#8220;Self-contained&#8221;, meaning a copy of the BCL and runtime are included\nwith the application.<\/li>\n<\/ul>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>This makes .NET MAUI applications suitable for running on &#8220;app stores&#8221; as no prerequisites such as installing a .NET runtime are required.<\/div><\/p>\n<ul>\n<li>Partially trimmed (<code>TrimMode=partial<\/code>), meaning that code within\nyour applications or NuGet packages are not trimmed by default.<\/li>\n<\/ul>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>This is a good default, as it is the most compatible with existing code and NuGet packages in the ecosystem.<\/div><\/p>\n<h2>Full Trimming<\/h2>\n<p>This is where full-trimming (<code>TrimMode=full<\/code>) can make an impact on\nyour application&#8217;s size. If you have a substantial amount of C# code\nor NuGet packages, you may be missing out on a significant application\nsize reduction.<\/p>\n<p>To opt into full trimming, you can add the following to your <code>.csproj<\/code> file:<\/p>\n<pre><code class=\"language-xml\">&lt;PropertyGroup&gt;\r\n  &lt;TrimMode&gt;full&lt;\/TrimMode&gt;\r\n&lt;\/PropertyGroup&gt;<\/code><\/pre>\n<p>For an idea on the impact of full trimming:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/02\/android-full-trimming.svg\" alt=\"Impact of Full Trimming on Android\" \/><\/p>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p> <a href=\"https:\/\/github.com\/jonathanpeppers\/MyPal\">MyPal<\/a> is a sample .NET MAUI application that is a useful comparison because of its usage of several common NuGet packages.<\/div><\/p>\n<p>See our <a href=\"https:\/\/learn.microsoft.com\/dotnet\/maui\/deployment\/trimming\">trimming .NET MAUI documentation<\/a> for more\ninformation on &#8220;full&#8221; trimming.<\/p>\n<h2>NativeAOT<\/h2>\n<p>Building upon full trimming, NativeAOT both relies on libraries being\ntrim-compatible <em>and<\/em> AOT-compatible. NativeAOT is a new runtime that\ncan improve startup time and reduce application size compared to\nexisting runtimes.<\/p>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>NativeAOT is not yet supported on Android, but is available on iOS, MacCatalyst, and Windows.<\/div><\/p>\n<p>To opt into NativeAOT:<\/p>\n<pre><code class=\"language-xml\">&lt;PropertyGroup&gt;\r\n  &lt;IsAotCompatible&gt;true&lt;\/IsAotCompatible&gt;\r\n  &lt;PublishAot&gt;true&lt;\/PublishAot&gt;\r\n&lt;\/PropertyGroup&gt;<\/code><\/pre>\n<p>For an idea on the impact of NativeAOT and application size:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/02\/nativeaot-filesize.svg\" alt=\"Impact on application size of NativeAOT\" \/><\/p>\n<p>And startup performance:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/02\/nativeaot-startup.svg\" alt=\"Impact on startup time of NativeAOT\" \/><\/p>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p><code>macOS<\/code> on the above graphs is running on <code>MacCatalyst<\/code>, the default for .NET MAUI applications running on Mac operating systems.<\/div><\/p>\n<p>See our <a href=\"https:\/\/learn.microsoft.com\/dotnet\/maui\/deployment\/nativeaot\">NativeAOT deployment documentation<\/a> for more\ninformation about this newly supported runtime.<\/p>\n<h2>NuGet Package Authors<\/h2>\n<p>As a NuGet package author, you may wish for your package to run in\neither fully trimmed or NativeAOT scenarios. This can be useful for\ndevelopers targeting .NET MAUI, mobile, or even self-contained ASP.NET\nmicroservices.<\/p>\n<p>To support NativeAOT, you will need to:<\/p>\n<ol>\n<li>Mark your assemblies as &#8220;trim-compatible&#8221; and &#8220;AOT-compatible&#8221;.<\/li>\n<li>Enable Roslyn analyzers for trimming and NativeAOT.<\/li>\n<li>Solve all the warnings.<\/li>\n<\/ol>\n<p>Begin with modifying your <code>.csproj<\/code> file:<\/p>\n<pre><code class=\"language-xml\">&lt;PropertyGroup&gt;\r\n  &lt;IsTrimmable&gt;true&lt;\/IsTrimmable&gt;\r\n  &lt;IsAotCompatible&gt;true&lt;\/IsAotCompatible&gt;\r\n&lt;\/PropertyGroup&gt;<\/code><\/pre>\n<p>These properties will enable Roslyn analyzers as well as include\n<code>[assembly: AssemblyMetadata]<\/code> information in the resulting .NET\nassembly. Depending on your library&#8217;s usage of features like\nSystem.Reflection, you could have either just a few warnings or\npotentially <em>many<\/em> warnings.<\/p>\n<p>See the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/deploying\/trimming\/prepare-libraries-for-trimming\">documentation on preparing libraries for\ntrimming<\/a> for more information.<\/p>\n<h2>XAML and Trimming<\/h2>\n<p>Sometimes, taking advantage of NativeAOT in your app can be as easy as\nadding a property to your project file. However, for many .NET MAUI\napplications, there can be a lot of warnings to solve. The NativeAOT\ncompiler removes unnecessary code and metadata to make the app smaller\nand faster. However, this requires understanding which types can be\ncreated and which methods can and cannot be called at runtime. This is\noften impossible to do in code which heavily uses System.Reflection.\nThere are two areas in .NET MAUI which fall into this category: XAML\nand data-binding.<\/p>\n<h3>Compiled XAML<\/h3>\n<p>Loading XAML at runtime provides flexibility and enables features like\nXAML hot reload. XAML can instantiate any class in the whole app, the\n.NET MAUI SDK, and referenced NuGet packages. XAML can also set values\nto any property.<\/p>\n<p>Conceptually, loading a XAML layout at runtime requires:<\/p>\n<ol>\n<li>Parsing the XML document.<\/li>\n<li>Looking up the control types based on the XML element names using <code>Type.GetType(xmlElementName)<\/code>.<\/li>\n<li>Creating new instances of the controls using <code>Activator.CreateInstance(controlType)<\/code>.<\/li>\n<li>Converting the raw string XML attribute values into the target type of the property.<\/li>\n<li>Setting properties based on the names of the XML attributes.<\/li>\n<\/ol>\n<p>This process can not only be slow, but it presents a great challenge\nfor NativeAOT. For example, the trimmer does not know which types\nwould be looked up using the <code>Type.GetType<\/code> method. This means that\neither the compiler would need to keep all the classes from the whole\n.NET MAUI SDK and all the NuGet packages in the final app, or the\nmethod might not be able to find the types declared in the XML input\nand fail at runtime.<\/p>\n<p>Fortunately, .NET MAUI has a solution &#8211; <a href=\"https:\/\/learn.microsoft.com\/dotnet\/maui\/xaml\/xamlc\">XAML compilation<\/a>.\nThis turns XAML into the actual code for the <code>InitializeComponent()<\/code>\nmethod at build time. Once the code is generated, the NativeAOT\ncompiler has all the information it needs to trim your app.<\/p>\n<p>In .NET 9, we implemented the last remaining XAML features that the\ncompiler could not handle in previous releases, especially compiling\nbindings. Lastly, if your app relies on loading XAML at runtime,\nNativeAOT might not be suitable for your application.<\/p>\n<h3>Compiled Bindings<\/h3>\n<p>A binding ties together a source property with a target property. When\nthe source changes, the value is propagated to the target.<\/p>\n<p>Bindings in .NET MAUI are defined using a <code>string<\/code> &#8220;path&#8221;. This path\nresembles C# expressions for accessing properties and indexers. When\nthe binding is applied to a source object, .NET MAUI uses\nSystem.Reflection to follow the path to access the desired source\nproperty. This suffers from the same problems as loading XAML at\nruntime, because the trimmer does not know which properties could be\naccessed by reflection and so it does not know which properties it can\nsafely trim from the final application.<\/p>\n<p>When we know the type of the source object at build time from\n<code>x:DataType<\/code> attributes, we can compile the binding path into a simple\ngetter method (and a setter method for two-way bindings). The compiler\nwill also ensure that the binding listens to any property changes\nalong the binding path of properties that implement\n<code>INotifyPropertyChanged<\/code>.<\/p>\n<p>The XAML compiler could already compile most bindings in .NET 8 and\nearlier. In .NET 9 we made sure any binding in your XAML code can be\ncompiled. Learn more about this feature in the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/maui\/fundamentals\/data-binding\/compiled-bindings\">compiled bindings\ndocumentation<\/a>.<\/p>\n<h3>Compiled bindings in C#<\/h3>\n<p>The only supported way of defining bindings in C# code up until .NET 8\nhas been using a <code>string<\/code>-based path. In .NET 9, we are adding a new\nAPI which allows us to compile the binding using a source generator:<\/p>\n<pre><code class=\"language-c#\">\/\/ .NET 8 and earlier\r\nmyLabel.SetBinding(Label.TextProperty, \"Text\");\r\n\r\n\/\/ .NET 9\r\nmyLabel.SetBinding(Label.TextProperty, static (Entry nameEntry) =&gt; nameEntry.Text);<\/code><\/pre>\n<p>The <code>Binding.Create()<\/code> method is also an option, for when you need to\nsave the <code>Binding<\/code> instance for later use:<\/p>\n<pre><code class=\"language-c#\">var nameBinding = Binding.Create(static (Entry nameEntry) =&gt; nameEntry.Text);<\/code><\/pre>\n<p>.NET MAUI&#8217;s source generator will compile the binding the same way the\nXAML compiler does. This way the binding can be fully analyzed by the\nNativeAOT compiler.<\/p>\n<p>Even if you aren&#8217;t planning to migrate your application to NativeAOT,\ncompiled bindings can improve the general performance of the binding.\nTo illustrate the difference, let&#8217;s use <code>BenchmarkDotNet<\/code> to measure\nthe difference between the calls to <code>SetBinding()<\/code> on Android using the\nMono runtime:<\/p>\n<pre><code class=\"language-c#\">\/\/ dotnet build -c Release -t:Run -f net9.0-android\r\n\r\npublic class SetBindingBenchmark\r\n{\r\n    private readonly ContactInformation _contact = new ContactInformation(new FullName(\"John\"));\r\n    private readonly Label _label = new();\r\n\r\n    [GlobalSetup]\r\n    public void Setup()\r\n    {\r\n        DispatcherProvider.SetCurrent(new MockDispatcherProvider());\r\n        _label.BindingContext = _contact;\r\n    }\r\n\r\n    [Benchmark(Baseline = true)]\r\n    public void Classic_SetBinding()\r\n    {\r\n        _label.SetBinding(Label.TextProperty, \"FullName.FirstName\");\r\n    }\r\n\r\n    [Benchmark]\r\n    public void Compiled_SetBinding()\r\n    {\r\n        _label.SetBinding(Label.TextProperty, static (ContactInformation contact) =&gt; contact.FullName?.FirstName);\r\n    }\r\n\r\n    [IterationCleanup]\r\n    public void Cleanup()\r\n    {\r\n        _label.RemoveBinding(Label.TextProperty);\r\n    }\r\n}<\/code><\/pre>\n<p>When I ran the benchmark on Samsung Galaxy S23, I got the following results:<\/p>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th style=\"text-align: right;\">Mean<\/th>\n<th style=\"text-align: right;\">Error<\/th>\n<th style=\"text-align: right;\">StdDev<\/th>\n<th style=\"text-align: right;\">Ratio<\/th>\n<th style=\"text-align: right;\">RatioSD<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Classic_SetBinding<\/td>\n<td style=\"text-align: right;\">67.81 us<\/td>\n<td style=\"text-align: right;\">1.338 us<\/td>\n<td style=\"text-align: right;\">1.787 us<\/td>\n<td style=\"text-align: right;\">1.00<\/td>\n<td style=\"text-align: right;\">0.04<\/td>\n<\/tr>\n<tr>\n<td>Compiled_SetBinding<\/td>\n<td style=\"text-align: right;\">30.61 us<\/td>\n<td style=\"text-align: right;\">0.629 us<\/td>\n<td style=\"text-align: right;\">1.182 us<\/td>\n<td style=\"text-align: right;\">0.45<\/td>\n<td style=\"text-align: right;\">0.02<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The classic binding needs to first parse the <code>string<\/code>-based path and\nthen use System.Reflection to get the current value of the source.\nEach subsequent update of the source property will also be faster with\nthe compiled binding:<\/p>\n<pre><code class=\"language-c#\">\/\/ dotnet build -c Release -t:Run -f net9.0-android\r\n\r\npublic class UpdateValueTwoLevels\r\n{\r\n    ContactInformation _contact = new ContactInformation(new FullName(\"John\"));\r\n    Label _label = new();\r\n\r\n    [GlobalSetup]\r\n    public void Setup()\r\n    {\r\n        DispatcherProvider.SetCurrent(new MockDispatcherProvider());\r\n        _label.BindingContext = _contact;\r\n    }\r\n\r\n    [IterationSetup(Target = nameof(Classic_UpdateWhenSourceChanges))]\r\n    public void SetupClassicBinding()\r\n    {\r\n        _label.SetBinding(Label.TextProperty, \"FullName.FirstName\");\r\n    }\r\n\r\n    [IterationSetup(Target = nameof(Compiled_UpdateWhenSourceChanges))]\r\n    public void SetupCompiledBinding()\r\n    {\r\n        _label.SetBinding(Label.TextProperty, static (ContactInformation contact) =&gt; contact.FullName?.FirstName);\r\n    }\r\n\r\n    [Benchmark(Baseline = true)]\r\n    public void Classic_UpdateWhenSourceChanges()\r\n    {\r\n        _contact.FullName.FirstName = \"Jane\";\r\n    }\r\n\r\n    [Benchmark]\r\n    public void Compiled_UpdateWhenSourceChanges()\r\n    {\r\n        _contact.FullName.FirstName = \"Jane\";\r\n    }\r\n\r\n    [IterationCleanup]\r\n    public void Reset()\r\n    {\r\n        _label.Text = \"John\";\r\n        _contact.FullName.FirstName = \"John\";\r\n        _label.RemoveBinding(Label.TextProperty);\r\n    }\r\n}<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th style=\"text-align: right;\">Mean<\/th>\n<th style=\"text-align: right;\">Error<\/th>\n<th style=\"text-align: right;\">StdDev<\/th>\n<th style=\"text-align: right;\">Ratio<\/th>\n<th style=\"text-align: right;\">RatioSD<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Classic_UpdateWhenSourceChanges<\/td>\n<td style=\"text-align: right;\">46.06 us<\/td>\n<td style=\"text-align: right;\">0.934 us<\/td>\n<td style=\"text-align: right;\">1.369 us<\/td>\n<td style=\"text-align: right;\">1.00<\/td>\n<td style=\"text-align: right;\">0.04<\/td>\n<\/tr>\n<tr>\n<td>Compiled_UpdateWhenSourceChanges<\/td>\n<td style=\"text-align: right;\">30.85 us<\/td>\n<td style=\"text-align: right;\">0.634 us<\/td>\n<td style=\"text-align: right;\">1.295 us<\/td>\n<td style=\"text-align: right;\">0.67<\/td>\n<td style=\"text-align: right;\">0.03<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The differences for a single binding aren&#8217;t that dramatic but they add\nup. This can be noticeable on complex pages with many bindings or when\nscrolling lists like <code>CollectionView<\/code> or <code>ListView<\/code>.<\/p>\n<p>The full <a href=\"https:\/\/github.com\/simonrozsival\/maui9-bindings-benchmark\">source code of the above benchmarks<\/a> is available on GitHub.<\/p>\n<h2>Profiling .NET MAUI Applications<\/h2>\n<p>Attaching <code>dotnet-trace<\/code> to a .NET MAUI application, allows you to get\nprofiling information in formats like <code>.nettrace<\/code> and <code>.speedscope<\/code>.\nThese give you CPU sampling information about the time spent in each\nmethod in your application. This is quite useful for finding where\ntime is spent in the startup or general performance of your .NET\napplications. Likewise, <code>dotnet-gcdump<\/code> can take memory snapshots of\nyour application that display every managed C# object in memory.\n<code>dotnet-dsrouter<\/code> is a requirement for connecting <code>dotnet-trace<\/code> to a\nremote device, and so this is not needed for desktop applications.<\/p>\n<p>You can install these tools with:<\/p>\n<pre><code class=\"language-bash\">$ dotnet tool install -g dotnet-trace\r\nYou can invoke the tool using the following command: dotnet-trace\r\nTool 'dotnet-trace' was successfully installed.\r\n$ dotnet tool install -g dotnet-dsrouter\r\nYou can invoke the tool using the following command: dotnet-dsrouter\r\nTool 'dotnet-dsrouter' was successfully installed.\r\n$ dotnet tool install -g dotnet-gcdump\r\nYou can invoke the tool using the following command: dotnet-gcdump\r\nTool 'dotnet-gcdump' was successfully installed.<\/code><\/pre>\n<p>From here, instructions differ slightly for each platform, but\ngenerally the steps are:<\/p>\n<ol>\n<li>Build your application in <code>Release<\/code> mode. For Android, toggle\n<code>&lt;AndroidEnableProfiler&gt;true&lt;\/AndroidEnableProfiler&gt;<\/code> in your\n<code>.csproj<\/code> file, so the required Mono diagnostic components are\nincluded in the application.<\/li>\n<li>If profiling mobile, run <code>dotnet-dsrouter android<\/code> (or\n<code>dotnet-dsrouter ios<\/code>, etc.) on your development machine.<\/li>\n<li>Configure environment variables, so the application can connect to\nthe profiler. For example, on Android:<\/p>\n<pre><code class=\"language-bash\">$ adb reverse tcp:9000 tcp:9001\r\n# no output\r\n$ adb shell setprop debug.mono.profile '127.0.0.1:9000,nosuspend,connect'\r\n# no output<\/code><\/pre>\n<\/li>\n<li>Run your application.<\/li>\n<li>Attach <code>dotnet-trace<\/code> (or <code>dotnet-gcdump<\/code>) to the application,\nusing the PID of <code>dotnet-dsrouter<\/code>:<\/p>\n<pre><code class=\"language-bash\">$ dotnet-trace ps\r\n38604  dotnet-dsrouter  ~\/.dotnet\/tools\/dotnet-dsrouter.exe  ~\/.dotnet\/tools\/dotnet-dsrouter.exe android\r\n\r\n$ dotnet-trace collect -p 38604 --format speedscope\r\nNo profile or providers specified, defaulting to trace profile 'cpu-sampling'\r\n\r\nProvider Name                           Keywords            Level               Enabled By\r\nMicrosoft-DotNETCore-SampleProfiler     0x0000F00000000000  Informational(4)    --profile \r\nMicrosoft-Windows-DotNETRuntime         0x00000014C14FCCBD  Informational(4)    --profile \r\n\r\nWaiting for connection on \/tmp\/maui-app\r\nStart an application with the following environment variable: DOTNET_DiagnosticPorts=\/tmp\/maui-app<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>For iOS, macOS, and MacCatalyst, see the <a href=\"https:\/\/github.com\/dotnet\/maui\/wiki\/Profiling-.NET-MAUI-Apps#ios-mac-etc\">iOS profiling wiki\npage<\/a> for more information.<\/p>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>For Windows applications, you might just consider using Visual Studio&#8217;s built-in profiling tools, but <code>dotnet-trace collect -- C:\\path\\to\\an\\executable.exe<\/code> is also an option.<\/div><\/p>\n<p>Now that you&#8217;ve collected a file containing performance information,\nopening them to view the data is the next step:<\/p>\n<ul>\n<li><code>dotnet-trace<\/code> by default outputs <code>.nettrace<\/code> files, which can be\nopened in <a href=\"https:\/\/github.com\/microsoft\/perfview\">PerfView<\/a> or Visual Studio.<\/li>\n<li><code>dotnet-trace collect --format speedscope<\/code> outputs <code>.speedscope<\/code>\nfiles, which can be opened in the <a href=\"https:\/\/www.speedscope.app\/\">Speedscope web app<\/a>.<\/li>\n<li><code>dotnet-gcdump<\/code> outputs <code>.gcdump<\/code> files, which can be opened in\n<a href=\"https:\/\/github.com\/microsoft\/perfview\">PerfView<\/a> or Visual Studio. Note that there is not\ncurrently a good option to open these files on macOS.<\/li>\n<\/ul>\n<p>In the future, we hope to make profiling .NET MAUI applications easier\nin both future releases of the above .NET diagnostic tooling and\nVisual Studio.<\/p>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>Note that the NativeAOT runtime does not have support for <code>dotnet-trace<\/code> and performance profiling. You can use the other supported runtimes for this, or use native profiling tools instead such as Xcode&#8217;s Instruments.<\/div><\/p>\n<p>See the <a href=\"https:\/\/aka.ms\/profile-maui\">profiling .NET MAUI wiki page<\/a> for links to\ndocumentation on each platform or a <a href=\"https:\/\/youtu.be\/hIYwz5dsZ6M\">profiling demo on\nYouTube<\/a> for a full walkthrough.<\/p>\n<h2>Conclusion<\/h2>\n<p>.NET 9 introduces performance enhancements for .NET MAUI applications\nthrough full trimming and NativeAOT. These features enable developers\nto create more efficient and responsive applications by reducing\napplication size and improving startup times. By leveraging tools like\n<code>dotnet-trace<\/code> and <code>dotnet-gcdump<\/code>, developers can gain insights into\ntheir application&#8217;s performance.<\/p>\n<p>For a full rundown on .NET MAUI trimming and NativeAOT, see the\n<a href=\"https:\/\/youtu.be\/dcUN7c2w-Rg\">.NET Conf 2024 session<\/a> on the topic.<\/p>\n<p><iframe width=\"800\" height=\"450\" src=\"https:\/\/www.youtube.com\/embed\/dcUN7c2w-Rg?si=e57NTs0-pVKj9NIW\" allowfullscreen><\/iframe><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Optimize .NET MAUI application size and startup times with trimming and NativeAOT. Learn about `dotnet-trace` and `dotnet-gcdump` for measuring performance.<\/p>\n","protected":false},"author":1345,"featured_media":55621,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7233,756,3009,489],"tags":[7797,7238],"class_list":["post-55620","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-maui","category-csharp","category-performance","category-xaml","tag-dotnet-9","tag-net-maui"],"acf":[],"blog_post_summary":"<p>Optimize .NET MAUI application size and startup times with trimming and NativeAOT. Learn about `dotnet-trace` and `dotnet-gcdump` for measuring performance.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/55620","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/1345"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=55620"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/55620\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/55621"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=55620"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=55620"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=55620"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}