Nexus-Mods / NexusMods.App

Home of the development of the Nexus Mods App
https://nexus-mods.github.io/NexusMods.App/
GNU General Public License v3.0
872 stars 43 forks source link

Verify App Publishing Settings #793

Closed halgari closed 8 months ago

halgari commented 9 months ago

Bug Report

Summary

I've noticed some very bad startup times on the app, even during the sprint demo a few weeks back. I've replicated this locally by not publishing with PublishReadyToRun, or forgetting the -r <arch> flag. We should verify that all this is being set correctly and that we are also using --self-contained so that we don't require users to install .NET 8.0

Steps to reproduce

Run the app with a normal publish command, and then again with the above flags. Startup times go from 20sec without those flags to .5 seconds with them.

Sewer56 commented 9 months ago

I'm currently unable to reproduce with any of the following:

On either Windows or Linux.

Note: There are some publishing bugs:

Consider running git clean -xdf between runs when testing these things

General Observations

After building, I observe 2-4s delay in process creation (8s on a laptop), on Windows only:

I have identified it as hostile interference from Windows Defender.

It seems to halt startup:

Appendix: Replicating our CI Stuff

I also replicated our CI setup as a sanity test.

dotnet tool install -g KuiperZone.PupNet
pupnet -y -k zip -p DefineConstants=INSTALLATION_METHOD_ARCHIVE

Our publish settings: https://github.com/Nexus-Mods/NexusMods.App/blob/75e7bf5948cc427286220b920ed4781eb07b340c/src/NexusMods.App/app.pupnet.conf#L32

Not sure why our settings have TieredCompilation=true, it's redundant as TC has been on by default since .NET Core 3.0.

In any case, no issues with our CI setup.

Appendix: Checking Single File Setting

I noticed the native .NET Runtime components are not in the output folder, i.e. clrgc.dll and friends in single file builds.

In previous versions of .NET, these DLLs would be extracted to disk before being used if they are embedded (controlled via IncludeNativeLibrariesForSelfExtract .csproj property). They would extract to %temp%/.net.

Something probably changed regarding this in .NET 8. So I did what any reasonable person would do (whipping out IDA, of course 😉). Anyway, turns out that the Runtime now uses a version of SingleFileHost (<= bootstrapper that runs SingleFile .NET Apps) with the native dependencies statically compiled, avoiding separate DLLs.

Appendix: CPU Features

When investigating, I also considered what kind of differences there might be hardware wise between your machine, the Razer Blades at the office and what I have at home.

I figured the culprit might have been AVX512, so I kindly borrowed my elders' laptop (the only AVX512 machine in my household) to give it a go. I could not reproduce it there either; with all combinations.

Appendix: Approximate Numbers

SingleProcessApp Overhead

~0.4s

Logging Start to MainWindow

Scenario Time
No R2R 1.5s
R2R 0.9s

Theoretical I/O overhead

(There's also some additional process creation overhead, which is longer on Windows than on Linux, but that's kind of hard to even eyeball measure)

Assuming no interference from Defender of Background load. Assuming Single File, as multi-file performance is hard to predict (random reads are much more unpredictable than sequential).

R2R Self Contained:

Scenario Time
NVMe 0.09s
SATA SSD 0.64s
HDD 1.50s

No R2R Self Contained:

Scenario Time
NVMe 0.05s
SATA SSD 0.30s
HDD 0.72s