Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the load time performance of Blazor WebAssembly apps on low-end mobile devices #42284

Open
danroth27 opened this issue Jun 18, 2022 · 98 comments
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly Pillar: Technical Debt Priority:1 Work that is critical for the release, but we could probably ship without triaged
Milestone

Comments

@danroth27
Copy link
Member

We've been hearing reports from users that load performance of Blazor WebAssembly apps on low-end mobile devices can be very slow. For example, this user on Twitter reported "bootstrapping is very slow on low-end mobile devices (~10s), caching doesn't help this". We should investigate where the time is being spent on these devices and evaluate options for improvement.

  • If the issue is WASM compilation time, that's on the browser to improve.
  • If the issue is .NET runtime startup time, preinitialization might help.
  • Perhaps we could also cache the preinitialized results with a snapshot of the memory.
  • Or maybe these apps are doing something dynamic on startup that's slow.

The first step is to collect some performance profiles. Any help is appreciated! 🙏

@danroth27
Copy link
Member Author

danroth27 commented Jun 18, 2022

Some more examples of this feedback:

https://www.reddit.com/r/Blazor/comments/iorby5/why_is_blazor_loading_so_long/
https://twitter.com/lupusa1/status/1260309165777526784: "I mostly work on blazor wasm and it is slow on mobile, so for now I try to be prioritized to desktop solutions."
https://twitter.com/Savaged_US/status/1115369807019425793 - "Blazor and dotnet are just too slow for now (especially on low-power mobile devices)"
https://twitter.com/Tibcsi_/status/1304509087598682116: "Blazor webassembly is still slow on a mid range mobile"

@TanayParikh TanayParikh added feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly area-blazor Includes: Blazor, Razor Components Perf labels Jun 19, 2022
@javiercn javiercn removed the Perf label Jun 19, 2022
@javiercn javiercn added this to the .NET 8 Planning milestone Jun 19, 2022
@danroth27
Copy link
Member Author

Another report of 15s load time on mobile in this issue: #41909

@rogihee
Copy link
Contributor

rogihee commented Jun 29, 2022

I experience this as well on my oldest iPad (5th gen) - refreshing is still slow (~5-7s of pure loading), so caching is not helping. Nothing fancy in the app. How can we help profile this?

@mandeep-sps
Copy link

Hi Team,

I have a .Net 6 Blazor WASM app with Server Hosted Code. Before Publishing the APP I have added a few Config lines in the project file like this:-

  1. <TargetFramework>net6.0</TargetFramework>
    
  2. <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
    
  3. <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
    
  4. <PublishTrimmed>true</PublishTrimmed>
    
  5. <BlazorEnableCompression>true</BlazorEnableCompression>
    
  6. <InvariantGlobalization>true</InvariantGlobalization>
    
  7. <TrimMode>link</TrimMode>
    
  8. <!--<RunAOTCompilation>true</RunAOTCompilation>-->
    

and Added the Brotli Compression Decoce.js file and some code on the Index.html Page. Now my APP downloads only 1.5 MB of resources and Its loads into 1.5 Secs. SO I have Optimized my app using these settings.

image

If I am anything missing Please suggest me.

Thanks

@Alerinos
Copy link

Alerinos commented Jul 4, 2022

@mandeep-sps The problem occurs on older devices. Use throttling and do more tests

@emorell96
Copy link

Is this normal? I am testing my own application and I've done all the required things: do not use AOT (bigger files), use brotli (dynamic and static), use http3 and http2, etc... and I've got the load times of the app on my dev machine to be around 2.5 s which is acceptable. But where I am seeing the biggest issue is here:
image

So Blazor finishes loading its files at the 400 ms mark, but the first application request is ran a whole second later:

image

image

So is it normal for blazor to take more than a second in starting up? It feels awfully long tbh.

@ScottKane
Copy link

For my Blazor WASM app initial loading is taking 5-10 seconds which is bad enough but on click events from buttons are taking around 2 seconds to fire when accessing the site from a mobile device and often not firing at all resulting in a user having to repeatedly press buttons to get something to happen (doesn't happen from a PC web browser) which is obviously making the app extremely sluggish to use. I would understand if these buttons were submitting data to the server, but when they only trigger a modal to pop up with everything happening locally to the client, I would expect this to be a lot faster.

@danroth27
Copy link
Member Author

@ScottKane For the slow event firing issues your seeing, could you please open a separate issue with the specs of the devices where you see this problem including the browser version and a repro project that demonstrates the performance issue?

@emorell96
Copy link

@danroth27 is the delay of about a second between Blazor assemblies downloading and the app starting its function normal? (see above: #42284 (comment))

@ScottKane
Copy link

ScottKane commented Aug 4, 2022

@danroth27 I opened #43090 for this, I'm currently rebuilding the client from scratch to figure at at what point the performance dies. So far the only thing I can point to causing it is CascadingValue Value="_hubConnection" as the new client doesn't have that yet and seems to be performing fine even without AOT

@Shalxxx
Copy link

Shalxxx commented Aug 12, 2022

A lot of complaints are about the initial start speed because of several MBs of runtime DLLs that must be first downloaded while on slow mobile networks.

Is there any technical difficulty that prevents rendering the page on the server and sent to the device while DLLs are loading in the background? And seamlessly switch from server-generated-client-side to client-side without specifically implementing server-side? Such a hybrid solution would solve the main issue of the Blazor.

@charlesroddie
Copy link

A lot of complaints are about the initial start speed because of several MBs of runtime DLLs that must be first downloaded while on slow mobile networks.

A large contributor is #35302 .

@danroth27
Copy link
Member Author

Is there any technical difficulty that prevents rendering the page on the server and sent to the device while DLLs are loading in the background?

@Shalxxx Blazor already supports prerendering from the server.

And seamlessly switch from server-generated-client-side to client-side without specifically implementing server-side? Such a hybrid solution would solve the main issue of the Blazor.

This is something we hope to explore for .NET 8: #38128

@awillSoftwares
Copy link

@danroth27 Doens't solve. The page become unresponsive until the client completely load the big bloated binaries.
Prerendering works well when the client load fast enough.
Prerendering doesn't work well when system render some content that depends on authentication.
Server-side render is good only for search index crowlers, or maybe not even that.

@danroth27
Copy link
Member Author

@awillSoftwares You're correct that prerendering only improves the perceived load time of the page. We are discussing how we might in the future enable a combined model where the app starts off with Blazor Server and then transitions to Blazor WebAssembly after the app has been downloaded. Prerendering with authentication is also something we're working on for .NET 7: #27592.

@cirrusone
Copy link

cirrusone commented Aug 30, 2022

@danroth27 Can you also consider what happens if user device/browser does not support WebAssembly. Currently the User-Agent header appears to be the only way to detect device details which is pretty 'wild'. It would be nice to have some property or method such as .IsWebAssemblySupported() which could test if WebAssembly will actually run on device but without freezing or crashing the application. This would be useful for manual switching between Server/Wasm.

@yugabe
Copy link

yugabe commented Sep 2, 2022

@cirrusone most current browsers support WebAssembly, but you won't be able to detect it reliably on the server side. On the client, it's basically as easy as testing for the existence of the window global variable WebAssembly. Related: How can I check if a browser supports WebAssembly? (StackOverflow)

There are some instances when WebAssembly itself is supported, but not enabled. The Edge browser does disable WebAssembly support in some cases when using "Enhanced security", or a group policy might disable it for a company. It essentially means checking for the same thing as above.

@sajjadarashhh
Copy link

I have same issue in .net 6 blazor wasm on low-level(desktop) devices
and i tried to publish aot mode to help them
But in aot mode my app is slower in loadtime,runtime and bigger than normal mode.

@mandeep-sps
Copy link

mandeep-sps commented Sep 5, 2022

@sajjadarashhh Hey Please try to Add Brotli Compressresion into your WASM Project. I am sharing the code Below.

<script type="module"> import { BrotliDecode } from './decode.js'; Blazor.start({ loadBootResource: function (type, name, defaultUri, integrity) { if (type !== 'dotnetjs' && location.hostname !== 'localhost') { return (async function () { const response = await fetch(defaultUri + '.br', { cache: 'no-cache' }); if (!response.ok) { throw new Error(response.statusText); } const originalResponseBuffer = await response.arrayBuffer(); const originalResponseArray = new Int8Array(originalResponseBuffer); const decompressedResponseArray = BrotliDecode(originalResponseArray); const contentType = type === 'dotnetwasm' ? 'application/wasm' : 'application/octet-stream'; return new Response(decompressedResponseArray, { headers: { 'content-type': contentType } }); })(); } } }); </script>
and Some Line of code into Proj. file May be That will Decrease the first load time from your WASM project. Make sure are you using Dotnet 6 SDK.
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
<PublishTrimmed>true</PublishTrimmed>
<BlazorEnableCompression>true</BlazorEnableCompression>
<InvariantGlobalization>true</InvariantGlobalization>
<TrimMode>link</TrimMode>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!--<RunAOTCompilation>true</RunAOTCompilation>-->

@GeeSuth
Copy link

GeeSuth commented Sep 5, 2022

@mandeep-sps
How does this actually work?!!
decode.js where come from?

@jirisykora83
Copy link

@mandeep-sps
Copy link

mandeep-sps commented Sep 5, 2022

@yugabe @jirisykora83 I have added the Brotli Compression into the WASM App. It's working fine at my end. BR applied to all the DLLs files and as well as to Static Files.
Please check my Staging Demo Website for Reference purposes.
http://182.75.88.147:8126/

@jirisykora83
Copy link

@yugabe @jirisykora83 I have added the Brotli Compression into the WASM App. It's working fine at my end. BR applied to all the DLLs files and as well as to Static Files. Please check my Staging Demo Website for Reference purposes. http://182.75.88.147:8126/

I check your site and you are right i load slowly even at second load (where most of the dll should be cached). That dont have to be low-end mobile it is even on my desktop. Hard to say when currently there isnt easy way to profile WASM app.

note: Server location seems to be india (?) and my location is EU so "some" latency is expected.

@arthastheking113
Copy link

arthastheking113 commented Sep 9, 2022

We are discussing how we might in the future enable a combined model where the app starts off with Blazor Server and then transitions to Blazor WebAssembly after the app has been downloaded.

The switching model in runtime will be great since we take all advantages of both models.

@sgf
Copy link

sgf commented Sep 13, 2022

Blazor's performance always slow,never fast 😂

@anton-kharchenko
Copy link

Where developers can see the future features of Blazor, that are planning to be present in .Net 9?

@sajjadarashhh
Copy link

@antonKharchenko1997 you can see blazor .net 9 planning issues here

@megafetis
Copy link

megafetis commented Jun 15, 2024

There are 246 .wasm files In my non-trivial blazor webassembly project. Startup time in mobile device 3-5 seconds. May be will be useful to merge .wasm (.dll) files like ILMerge to improve load time performance?

Edit. First load without cache on mobile device: 11 sec. Net8

@danroth27
Copy link
Member Author

There are 246 .wasm files In my non-trivial blazor webassembly project. Startup time in mobile device 3-5 seconds. May be will be useful to merge .wasm (.dll) files like ILMerge to improve load time performance?

@megafetis That's a lot of assemblies! Can you confirm that you're reporting the number after the app is published and with the .NET trimmer enabled? Have you looked at reducing the number of dependencies, particularly dependencies on libraries that don't support trimming?

@MarkusRodler
Copy link
Contributor

There are 246 .wasm files In my non-trivial blazor webassembly project. Startup time in mobile device 3-5 seconds. May be will be useful to merge .wasm (.dll) files like ILMerge to improve load time performance?

@megafetis That's a lot of assemblies! Can you confirm that you're reporting the number after the app is published and with the .NET trimmer enabled? Have you looked at reducing the number of dependencies, particularly dependencies on libraries that don't support trimming?

In my projects I've got around 55-60 dependencies with no 3th party deps. So yes. 246 are really a very huge amount of deps. And in my opinion the amount of deps is not the actual problem. It's the boot-time from the whole runtime thats unfortunately slow. 😢

@danroth27
Copy link
Member Author

And in my opinion the amount of deps is not the actual problem. It's the boot-time from the whole runtime thats unfortunately slow.

@markus Is this just an opinion, or does it reflect an actual measurement? For example, how long does your app take to load the first time before anything is cached versus the second time when the app is already cached? Also, can you try with .NET 9 and see if the load time is significantly improved? We've already done quite a bit of work in .NET 9 the improve the runtime & Blazor startup performance.

@MarkusRodler
Copy link
Contributor

MarkusRodler commented Jun 17, 2024

And in my opinion the amount of deps is not the actual problem. It's the boot-time from the whole runtime thats unfortunately slow.

@markus Is this just an opinion, or does it reflect an actual measurement? For example, how long does your app take to load the first time before anything is cached versus the second time when the app is already cached? Also, can you try with .NET 9 and see if the load time is significantly improved? We've already done quite a bit of work in .NET 9 the improve the runtime & Blazor startup performance.

@danroth27 It's not only an opinion. I try really hard to make the app as performant as possible, but if I compare it with an JS approach it is very slow (but not that ugly to write 😄)
On a low end client it takes around 48 seconds on the very first load. with 3.6 MB of compressed data without making any request for the actual data to display. But thats fine for my use case because I don't care that much about first time usages. It uses the PWA approach and so I have to wait that much only one time. (App updates are in the background)

Off topic: I don't understand why the user has to load something from the TestPlatform. I don't have any references to that.
image

On subsequent runs it takes around 11 seconds with 167kB of data. And that is my problem. If I rebuild that with JS and no PWA it takes about 1.6 seconds.

The results are taken from the latest .NET 8 and I didn't test it with .NET 9 because it is not that straightforward to install .NET 9 within WSL2.

Disclaimer: Low-End device means: Wired Network, 8GB Ram, Intel Pentium J2900 2.41 GHz Processor with Windows 10.

Server.csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <LangVersion>12.0</LangVersion>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <PublishAot Condition="'$(Configuration)'=='Release'">true</PublishAot>
    </PropertyGroup>

    <ItemGroup>
        <ProjectReference Include="..\Client\Client.csproj" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" />
    </ItemGroup>

</Project>

Client.csproj:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <LangVersion>12.0</LangVersion>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <PublishTrimmed>true</PublishTrimmed> <!-- breaks hot reload if true -->
        <DebuggerSupport Condition="'$(Configuration)'=='Release'">false</DebuggerSupport>
        <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
        <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
        <EventSourceSupport>false</EventSourceSupport>
        <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
        <MetadataUpdaterSupport>false</MetadataUpdaterSupport>
        <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Dark.Component" Version="2024.6.2.305" />
        <PackageReference Include="Dark.CsharpExtension" Version="2024.6.10.69" />
        <PackageReference Include="Dark.EventSourcing.Client" Version="2024.6.2.330" />
        <PackageReference Include="Markdig" Version="0.37.0" />
        <PackageReference Include="MenuPlanner.Shared" Version="2024.6.10.143" />
        <PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
    </ItemGroup>

    <ItemGroup>
        <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
    </ItemGroup>

    <ItemGroup>
        <Watch Include="wwwroot\**\*.*" />
    </ItemGroup>

</Project>

@megafetis
Copy link

megafetis commented Jun 17, 2024

There are 246 .wasm files In my non-trivial blazor webassembly project. Startup time in mobile device 3-5 seconds. May be will be useful to merge .wasm (.dll) files like ILMerge to improve load time performance?

@megafetis That's a lot of assemblies! Can you confirm that you're reporting the number after the app is published and with the .NET trimmer enabled? Have you looked at reducing the number of dependencies, particularly dependencies on libraries that don't support trimming?

Hello @danroth27. I am glad, you answer. I am with blazor since netcore 3.0 preview!

  1. I have to enable only a partial trimming right now.
  2. Some of 246 wasm files loaded via LazyAssemblyLoader (13 files). Full trimming crashes my app.
  3. The app is a large web portal, aggregator. I need to divide a client part of the app to about 20-25 subprojects. Some of them are lazy loaded.
  4. 161 file are System.* libs. I Dont know why.
    BUT!
    It is not a big problem. It's a matter of time to decide about all safe trimmable files. Full trimming keeps 135 files instead of 246. But now i am not ready to enable full trimming.
    Common question is a suggestion about meging files to a united dll (wasm) "bundles". May will be usefull merge assets like in this lib https://github.com/dotnet/ILMerge.
    It will be great to load 5-10 wasm dependencies first load time, instead of hundreds. Just remember a good webpack experience to bundling many js files of third party libraries to elegant parts.
    Thanks.

@megafetis
Copy link

There are 246 .wasm files In my non-trivial blazor webassembly project. Startup time in mobile device 3-5 seconds. May be will be useful to merge .wasm (.dll) files like ILMerge to improve load time performance?

@megafetis That's a lot of assemblies! Can you confirm that you're reporting the number after the app is published and with the .NET trimmer enabled? Have you looked at reducing the number of dependencies, particularly dependencies on libraries that don't support trimming?

In my projects I've got around 55-60 dependencies with no 3th party deps. So yes. 246 are really a very huge amount of deps. And in my opinion the amount of deps is not the actual problem. It's the boot-time from the whole runtime thats unfortunately slow. 😢

Yes. I Achieved 42 points from 21 in Google pagespeed. First load on mobile device is about 11 seconds with many optimizing technics.

@danroth27
Copy link
Member Author

On subsequent runs it takes around 11 seconds with 167kB of data. And that is my problem. If I rebuild that with JS and no PWA it takes about 1.6 seconds.

@MarkusRodler That does seem concerning. Can you tell us what's in that 167kB of data that's still being downloaded after the app was cached? Also, how are you making the load time measurements? Are you just using the browser dev tools? Note that having the browser dev tools open will significantly slow things down. Even so, we'd still like to close the gap between Blazor and JS.

The results are taken from the latest .NET 8 and I didn't test it with .NET 9 because it is not that straightforward to install .NET 9 within WSL2.

Here's how we think you can install and try out .NET 9: https://learn.microsoft.com/dotnet/core/install/linux-debian#install-preview-versions.

Disclaimer: Low-End device means: Wired Network, 8GB Ram, Intel Pentium J2900 2.41 GHz Processor with Windows 10.

What browser and browser version are you using for testing?

<PackageReference Include="Dark.CsharpExtension" Version="2024.6.10.69" />

This dependency on Dark.CsharpExtension seems to pull in MSTest.Framework. Are you sure you want all that on the client?

@danroth27
Copy link
Member Author

Common question is a suggestion about merging files to a united dll (wasm) "bundles". May will be useful merge assets like in this lib https://github.com/dotnet/ILMerge.

@megafetis I'm not sure how much this will help if you have a modern HTTP/2 connection. It would be interesting though if someone wants to do some experiments with this and see if it makes a significant difference.

@MarkusRodler
Copy link
Contributor

@MarkusRodler That does seem concerning. Can you tell us what's in that 167kB of data that's still being downloaded after the app was cached?

Well that was my fault. The 167kB came from the Adblock extension. I disabled all of them and now nothing is transferred. In this run it took 11.4 seconds. So in the end it doesn't make any difference in load time performance.

Also, how are you making the load time measurements? Are you just using the browser dev tools? Note that having the browser dev tools open will significantly slow things down. Even so, we'd still like to close the gap between Blazor and JS.

Yes with dev tools open. But I also had it open for the JS-only variant ;-)

Here's how we think you can install and try out .NET 9: https://learn.microsoft.com/dotnet/core/install/linux-debian#install-preview-versions.

I tried it with .NET 8 previews back then but I didn't managed to do it. In the end I waited for the release and used the package manager.

What browser and browser version are you using for testing?

MS Edge of course 😄 Version: 126.0.2592.56

This dependency on Dark.CsharpExtension seems to pull in MSTest.Framework. Are you sure you want all that on the client?

No I don't want the test framework on the client. But I thought that would be stripped away through trimming?

@danroth27
Copy link
Member Author

No I don't want the test framework on the client. But I thought that would be stripped away through trimming?

@MarkusRodler Blazor by default uses the partial trim mode, which means that only assemblies that are marked as trimmable will get trimmed. It's likely that Dark.CsharpExtension hasn't been set up to support trimming yet. More details on authoring trimmable libraries and trimming options here:

@MarkusRodler
Copy link
Contributor

No I don't want the test framework on the client. But I thought that would be stripped away through trimming?

@MarkusRodler Blazor by default uses the partial trim mode, which means that only assemblies that are marked as trimmable will get trimmed. It's likely that Dark.CsharpExtension hasn't been set up to support trimming yet. More details on authoring trimmable libraries and trimming options here:

Oh it is set up for trimming. Here is the csproj:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <LangVersion>12.0</LangVersion>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <PackageId>Dark.CsharpExtension</PackageId>
        <Authors>Markus Rodler</Authors>
        <Company>Dark</Company>
        <PackageDescription>This package extends C# projects with various improvements</PackageDescription>
        <RepositoryUrl>https://github.com/MarkusRodler/Dark.CsharpExtension</RepositoryUrl>
        <PackageReadmeFile>README.md</PackageReadmeFile>
        <PackageLicenseExpression>LGPL-2.1-only</PackageLicenseExpression>
        <IsTrimmable>true</IsTrimmable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
    </ItemGroup>

    <ItemGroup>
        <None Include="../README.md" pack="true" PackagePath="." />
    </ItemGroup>

</Project>

@danroth27
Copy link
Member Author

Oh it is set up for trimming.

@MarkusRodler That will enable Dark.CsharpExtension to be trimmed, but it won't help for MSTest.TestFramework, which I believe isn't trimmable. The partial trim mode will unfortunately keep assemblies that aren't marked as trimmable, even if they're a dependency of a trimmable assembly.

@MarkusRodler
Copy link
Contributor

Oh it is set up for trimming.

@MarkusRodler That will enable Dark.CsharpExtension to be trimmed, but it won't help for MSTest.TestFramework, which I believe isn't trimmable. The partial trim mode will unfortunately keep assemblies that aren't marked as trimmable, even if they're a dependency of a trimmable assembly.

Ok I don't like that behaviour. But nevertheless, even without that dependency the bootstrap is really slow. I don't know what needs to be recalculated every time the app starts.

@danroth27
Copy link
Member Author

Ok I don't like that behaviour.

Yeah, it surprised me initially as well and I spent a bunch of time talking to the .NET trimmer folks talking about it. The partial trim mode is intentionally conservative. We'd like to enable support for full trimming in Blazor, but we need to remove a bunch of reflection logic first.

But nevertheless, even without that dependency the bootstrap is really slow. I don't know what needs to be recalculated every time the app starts.

We found in our .NET 9 startup perf investigations that a bunch of time is spent in JSON deserialization, so we switched to using the source generated APIs for that. We've also done a bunch of work on the runtime itself to optimize startup. It really would be helpful if you could try out the latest .NET 9 preview and let us know how much those changes improve your scenario. We'd be happy to help you work through any issues getting .NET 9 set up in your environment 🥺.

@MarkusRodler
Copy link
Contributor

We found in our .NET 9 startup perf investigations that a bunch of time is spent in JSON deserialization, so we switched to using the source generated APIs for that. We've also done a bunch of work on the runtime itself to optimize startup. It really would be helpful if you could try out the latest .NET 9 preview and let us know how much those changes improve your scenario. We'd be happy to help you work through any issues getting .NET 9 set up in your environment 🥺.

@danroth27 Ok I thought I could try it the "easy" way and just update the csproj-files and use the newer docker images (a link to all of the available tags would be cool in docker hub).
So I came up with these changes:
Client

        <TargetFramework>net9.0</TargetFramework>
        <LangVersion>13.0</LangVersion>
...
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0-preview.5.24306.11" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0-preview.5.24306.11" PrivateAssets="all" />

Server

        <TargetFramework>net9.0</TargetFramework>
        <LangVersion>13.0</LangVersion>
...
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0-preview.5.24306.11" />

Dockerfile
(Build)
mcr.microsoft.com/dotnet/nightly/sdk:8.0-jammy-aot => mcr.microsoft.com/dotnet/nightly/sdk:9.0-preview-noble-aot
(Publish)
mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot => mcr.microsoft.com/dotnet/nightly/runtime-deps:9.0.0-preview.6-noble-chiseled-aot

But it doesn't work:

#12 1.146   Determining projects to restore...
#12 7.107 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NET.ILLink.Tasks with version (>= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
#12 7.107 /app/Client/Client.csproj : error NU1102:   - Found 25 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
#12 7.107 /app/Client/Client.csproj : error NU1102:   - Found 0 version(s) in github [/app/Server/Server.csproj]
#12 7.107 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.DotNet.ILCompiler with version (>= 9.0.0-preview.6.24307.2)
#12 7.107 /app/Server/Server.csproj : error NU1102:   - Found 49 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.107 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
#12 7.108 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.NET.ILLink.Tasks with version (>= 9.0.0-preview.6.24307.2)
#12 7.108 /app/Server/Server.csproj : error NU1102:   - Found 25 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.108 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
#12 7.108 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NET.Sdk.WebAssembly.Pack with version (>= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
#12 7.108 /app/Client/Client.csproj : error NU1102:   - Found 18 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
#12 7.108 /app/Client/Client.csproj : error NU1102:   - Found 0 version(s) in github [/app/Server/Server.csproj]
#12 7.115 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Runtime.linux-x64 with version (= 9.0.0-preview.6.24307.2)
#12 7.115 /app/Server/Server.csproj : error NU1102:   - Found 164 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.115 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
#12 7.115 /app/Server/Server.csproj : error NU1102: Unable to find package runtime.linux-x64.Microsoft.DotNet.ILCompiler with version (= 9.0.0-preview.6.24307.2)
#12 7.115 /app/Server/Server.csproj : error NU1102:   - Found 49 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.115 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
#12 7.115 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.AspNetCore.App.Runtime.linux-x64 with version (= 9.0.0-preview.6.24309.2)
#12 7.115 /app/Server/Server.csproj : error NU1102:   - Found 164 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.11 ]
#12 7.115 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
#12 7.115 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Runtime.Mono.browser-wasm with version (= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
#12 7.115 /app/Client/Client.csproj : error NU1102:   - Found 89 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
#12 7.115 /app/Client/Client.csproj : error NU1102:   - Found 0 version(s) in github [/app/Server/Server.csproj]
#12 7.350   Failed to restore /app/Server/Server.csproj (in 5.62 sec).
#12 7.350   Failed to restore /app/Client/Client.csproj (in 5.62 sec).
#12 ERROR: process "/bin/sh -c dotnet publish Server     --output /app/publish     --runtime linux-x64     --self-contained true     /p:DebugType=None     /p:DebugSymbols=false" did not complete successfully: exit code: 1
------
 > [publish 4/5] RUN --mount=type=cache,target=/root/.nuget     --mount=type=cache,target=/app/artifacts     dotnet publish Server     --output /app/publish     --runtime linux-x64     --self-contained true     /p:DebugType=None     /p:DebugSymbols=false:
7.115 /app/Server/Server.csproj : error NU1102:   - Found 49 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
7.115 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
7.115 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.AspNetCore.App.Runtime.linux-x64 with version (= 9.0.0-preview.6.24309.2)
7.115 /app/Server/Server.csproj : error NU1102:   - Found 164 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.11 ]
7.115 /app/Server/Server.csproj : error NU1102:   - Found 0 version(s) in github
7.115 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Runtime.Mono.browser-wasm with version (= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
7.115 /app/Client/Client.csproj : error NU1102:   - Found 89 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
7.115 /app/Client/Client.csproj : error NU1102:   - Found 0 version(s) in github [/app/Server/Server.csproj]
7.350   Failed to restore /app/Server/Server.csproj (in 5.62 sec).
7.350   Failed to restore /app/Client/Client.csproj (in 5.62 sec).

@danroth27
Copy link
Member Author

danroth27 commented Jun 20, 2024

mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot => mcr.microsoft.com/dotnet/nightly/runtime-deps:9.0.0-preview.6-noble-chiseled-aot

@MarkusRodler That looks like a .NET 9 Preview 6 container, which hasn't shipped yet. Does mcr.microsoft.com/dotnet/nightly/runtime-deps:9.0.0-preview.5-noble-chiseled-aot work?

@richlander
Copy link
Member

Otherwise, you need a nuget.config with a .NET 9 entry, like: https://github.com/dotnet/dotnet-docker/blob/main/samples/releasesapi/nuget.config#L4

@MarkusRodler
Copy link
Contributor

mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot => mcr.microsoft.com/dotnet/nightly/runtime-deps:9.0.0-preview.6-noble-chiseled-aot

@MarkusRodler That looks like a .NET 9 Preview 6 container, which hasn't shipped yet. Does mcr.microsoft.com/dotnet/nightly/runtime-deps:9.0.0-preview.5-noble-chiseled-aot work?

On this page it looks like it is shipped: https://mcr.microsoft.com/en-us/product/dotnet/nightly/runtime-deps/tags
I switched to preview.5 and it didn't worked.

Otherwise, you need a nuget.config with a .NET 9 entry, like: https://github.com/dotnet/dotnet-docker/blob/main/samples/releasesapi/nuget.config#L4

That worked. I only had to switch to dotnet format instead of dotnet-format and <LangVersion>13.0</LangVersion> can not work atm.

Ok now I deployed the new version. "The results are back": Around 8 seconds for the bootstrap. I like that it improves and shaves off about 3 seconds. But there is still room for improvement. 😅

@danroth27
Copy link
Member Author

Thanks @MarkusRodler for trying that out! It sounds like we do have some more work to do to improve the startup performance 🤔.

To help us understand what's happening during those 8 seconds could you collect a browser performance trace for us? To do this, you'll need to first set <WasmNativeStrip>false</WasmNativeStrip> in your client project, delete bin & obj, and then republish the app so that the .NET WebAssembly runtime has all the debug info we need. Then, in the browser dev tools go to the Performance tab and hit record before hitting F5 to reload the site. After the site loads, stop the recording and hit the save button to generate the trace file.

@MarkusRodler
Copy link
Contributor

@danroth27 I tried setting that flag but to compile it needs wasm-tools. So I tried installing them, but it fails:

#12 [publish 4/6] RUN dotnet workload restore
#12 1.062 Installing workloads: wasm-tools
#12 1.094 
#12 1.738 Skipping NuGet package signature verification.
#12 1.858 Installing workload manifest microsoft.net.sdk.android version 34.99.0-preview.6.339...
#12 1.953 Installing workload manifest microsoft.net.sdk.ios version 17.2.9093-net9-p1...
#12 2.045 Installing workload manifest microsoft.net.sdk.maccatalyst version 17.2.9093-net9-p1...
#12 2.[153](https://github.com/MarkusRodler/dark-menuplanner-site/actions/runs/9675432056/job/26692973017#step:4:154) Installing workload manifest microsoft.net.sdk.macos version 14.2.9093-net9-p1...
#12 2.267 Installing workload manifest microsoft.net.sdk.maui version 9.0.0-preview.1.9978...
#12 2.358 Installing workload manifest microsoft.net.sdk.tvos version 17.2.9093-net9-p1...
#12 2.453 Installing workload manifest microsoft.net.workload.mono.toolchain.current version 9.0.0-preview.6.24324.3...
#12 2.563 Installing workload manifest microsoft.net.workload.mono.toolchain.net6 version 9.0.0-preview.6.24324.3...
#12 2.672 Installing workload manifest microsoft.net.workload.mono.toolchain.net7 version 9.0.0-preview.6.24324.3...
#12 2.785 Installing workload manifest microsoft.net.workload.mono.toolchain.net8 version 9.0.0-preview.6.24324.3...
#12 2.896 Installing workload manifest microsoft.net.sdk.aspire version 9.0.0-preview.4.24229.9...
#12 3.018 Workload installation failed. Rolling back installed packs...
#12 3.022 Installation rollback failed: Workload manifest dependency 'Microsoft.NET.Workload.Emscripten.Current' version '9.0.0-preview.6.24319.1' is lower than version '9.0.0-preview.7.24319.4' required by manifest 'microsoft.net.workload.mono.toolchain.current' [/usr/share/dotnet/sdk-manifests/9.0.100-preview.6/microsoft.net.workload.mono.toolchain.current/9.0.0-preview.6.24324.3/WorkloadManifest.json]
#12 3.025 Workload installation failed: Workload manifest dependency 'Microsoft.NET.Workload.Emscripten.Current' version '9.0.0-preview.6.24319.1' is lower than version '9.0.0-preview.7.24319.4' required by manifest 'microsoft.net.workload.mono.toolchain.current' [/usr/share/dotnet/sdk-manifests/9.0.100-preview.6/microsoft.net.workload.mono.toolchain.current/9.0.0-preview.6.24324.3/WorkloadManifest.json]
#12 ERROR: process "/bin/sh -c dotnet workload restore" did not complete successfully: exit code: 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly Pillar: Technical Debt Priority:1 Work that is critical for the release, but we could probably ship without triaged
Projects
None yet
Development

No branches or pull requests