You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With SDK builds taken between 4/8 and 4/10 we noticed a regression in startup time for applications published as single files.
Environment:
This was reproduced only on Windows (Linux is not affected) and can still be consistently. Same machine, Windows Defender disabled on the folder starting the app. Same parent folder between runs, though any AV should happen the same way on both builds.
Not specific to a single app
Here showing ASP.NET application without DI or middleware (Platform benchmark)
singlefilehost is grabbed from the SDK known apphost packs, so that seems to be the version that matters here. Unfortunately, that is still a big range for runtime: 10.0.0-preview.4.25174.9 to 10.0.0-preview.4.25207.7 - 659321f...9559b2a
I haven't figured out how to get a repro that isn't the full build and application run through crank though - which presumably I'd need to try any sort of fix.
When I unzipped the downloaded applications (from application.options.fetch) and then kickoff jobs to just run the executables, I see the same start time for both versions: ~500ms
I also tried to do a local version of crank's time to readyStateText measurement. I also saw the same elapsed times for both the 'good' and 'bad' versions.
usingvarprocess=newProcess{StartInfo=startInfo};varstopwatch=newStopwatch();boolfound=false;stringreadyText="Application started.";process.OutputDataReceived+=(sender,e)=>{if(e.Data!=null&&e.Data.StartsWith(readyText)){stopwatch.Stop();found=true;}};Console.WriteLine($"{startInfo.FileName}");stopwatch.Start();process.Start();process.BeginOutputReadLine();while(!found){Thread.Sleep(100);}process.Kill();Console.WriteLine($" Start time (ms): {stopwatch.ElapsedMilliseconds}");
I passed --application.collect true --application.collectStartup true to get traces (for the full build / run jobs, where this repros). Unfortunately, >70% of the stacks are reported as broken for both traces.
When I unzipped the downloaded applications (from application.options.fetch) and then kickoff jobs to just run the executables, I see the same start time for both versions: ~500ms
Another thing to note is that the 500ms from just running the output from the jobs from the base and regressed versions is on par with the reported regressed time when the job is to build/publish and run. It almost seems like a first run thing (single-file does pass assemblies to AMSI), but I'm not sure how that would happen.
Locally, if I try a similar build+run of the PlatformBenchmarks project and measure two executions of the output as in #116318 (comment), the first run is ~550ms and the second ~200ms. But I see the same thing for both 25208.4 and 25208.7
@sebastienros Is there any way to make crank use/upload a local SDK or patch a file in the one it downloads?
I haven't been able to figure out a local repro or get traces that aren't largely broken stacks (they show a difference in blocked time, but the stacks are too broken to see from what), but I think I may have narrowed it down a bit via trying different jobs with crank.
PlatformBenchmarks project with a commit that allows updating the KnownAppHostPack 10.0 version (corresponds to the singlefilehost that is used)
Ran jobs for the 10.0.0-preview.4.25207.7 runtime libraries and singlefilehost with the 10.0.100-preview.4.25208.4 and 10.0.100-preview.4.25208.7 SDKs - verified that both were using the 10.0.0-preview.4.25207.7 runtime libraries and singlefilehost
Saw the start time regression with just the SDK version difference.
That leaves the SDK itself as the only difference - dotnet/sdk@e378cb0...d4bbb19 - the dotnet/runtime update looks like the only interesting change. The only thing I could see that was changed there was Microsoft.NET.HostModel in edb0a06. That changed to writing out a temporary file for the bundle and then copying over the existing file (instead of updating it in place). I have no idea why that would change the start time on Windows, but it is the one thing I see that is shipped via the SDK and only affect single-file. That behaviour is also going to be changing with #116659 (cc @jtschuster)
If there's some way to kick off a crank job with a local/patched SDK, we could see if Microsoft.NET.HostModel is actually the difference in those runs.
Based on the above, it sounds like this regression may be specific to the startup of a just created app. That is, the first run after the app has just been built. This isn't as high a priority as the startup time of a shipping application and they don't necessarily correspond due to differences in signing/virus scanning.
I confirmed that this scenario is only first-run of a just-created app. It turns out this is from Windows Code Integrity checks. The perf lab machines appear to have that enabled, which would explain why I could not repro this locally as I do not. There is no effect on the startup of a deployed application (not just created on the machine). The cold start of a just-created app slows down, but the warm run of that is much improved. Closing this - real deployed apps are not affected and this is actually an improvement for just-created apps after the first run.
More details:
With Windows Code Integrity enabled, the entire image is hashed at startup (as part of CreateProcess). In order to do this, any dirty/modified pages must be flushed. The bundler was switched to writing the final single-file app via a file stream write rather than a memory-mapped write. The file stream write meant that much of the file is left dirty and must be reconciled on startup - with the memory-mapped write this was not necessary. However, the switch to the file stream write also meant that the results of the Code Integrity were cached - after the first run, the cost of the check goes away. With the memory-mapped write, the check was not cached and re-ran every app run. With copying over the app (as would be in shipped apps), this difference disappears - the check occurs on first-run and is cached.
Runs were all on aspnet-gold-win using the json scenario.
Running a just-created app:
Version
Cold (1st run)
Warm (2nd+ run)
10.0.100-preview.4.25207.27
2378 ms
2374 ms
10.0.100-preview.4.25209.44
2720 ms
164 ms
11.0.100-preview.6.26359.118
2710 ms
145 ms
With tracing on (ci.dll is Windows Code Integrity):
Version
Run
start-time
ci.dll samples
10.0.100-preview.4.25207.27
cold
3167 ms
1760
10.0.100-preview.4.25207.27
warm
3147 ms
1789
10.0.100-preview.4.25209.44
cold
3479 ms
1793
10.0.100-preview.4.25209.44
warm
266 ms
0
11.0.100-preview.6.26359.118
cold
3485 ms
1851
11.0.100-preview.6.26359.118
warm
269 ms
0
Running a copied app shows no difference - real deployment scenarios:
Activity
filipnavara commentedon Jun 4, 2025
Presumably that's x64, right? (ie. not x86, not arm64)
sebastienros commentedon Jun 4, 2025
@filipnavara In this case INTEL X64 Windows.
dotnet-policy-service commentedon Jun 4, 2025
Tagging subscribers to this area: @agocke, @vitek-karas, @VSadov
See info in area-owners.md if you want to be subscribed.
elinor-fung commentedon Jun 20, 2025
Slightly narrowed SDK versions:
10.0.100-preview.4.25208.4: 186ms10.0.100-preview.4.25208.7: 512mssinglefilehostis grabbed from the SDK known apphost packs, so that seems to be the version that matters here. Unfortunately, that is still a big range for runtime:10.0.0-preview.4.25174.9to10.0.0-preview.4.25207.7- 659321f...9559b2aI haven't figured out how to get a repro that isn't the full build and application run through crank though - which presumably I'd need to try any sort of fix.
When I unzipped the downloaded applications (from
application.options.fetch) and then kickoff jobs to just run the executables, I see the same start time for both versions: ~500msThis is my config:
elinor-fung commentedon Jun 20, 2025
I also tried to do a local version of
crank's time toreadyStateTextmeasurement. I also saw the same elapsed times for both the 'good' and 'bad' versions.elinor-fung commentedon Jun 23, 2025
I passed
--application.collect true --application.collectStartup trueto get traces (for the full build / run jobs, where this repros). Unfortunately, >70% of the stacks are reported as broken for both traces.Another thing to note is that the 500ms from just running the output from the jobs from the base and regressed versions is on par with the reported regressed time when the job is to build/publish and run. It almost seems like a first run thing (single-file does pass assemblies to AMSI), but I'm not sure how that would happen.
Locally, if I try a similar build+run of the
PlatformBenchmarksproject and measure two executions of the output as in #116318 (comment), the first run is ~550ms and the second ~200ms. But I see the same thing for both 25208.4 and 25208.7elinor-fung commentedon Jun 24, 2025
@sebastienros Is there any way to make
crankuse/upload a local SDK or patch a file in the one it downloads?I haven't been able to figure out a local repro or get traces that aren't largely broken stacks (they show a difference in blocked time, but the stacks are too broken to see from what), but I think I may have narrowed it down a bit via trying different jobs with
crank.PlatformBenchmarksproject with a commit that allows updating theKnownAppHostPack10.0 version (corresponds to thesinglefilehostthat is used)10.0.0-preview.4.25207.7runtime libraries andsinglefilehostwith the10.0.100-preview.4.25208.4and10.0.100-preview.4.25208.7SDKs - verified that both were using the10.0.0-preview.4.25207.7runtime libraries andsinglefilehostThat leaves the SDK itself as the only difference - dotnet/sdk@e378cb0...d4bbb19 - the dotnet/runtime update looks like the only interesting change. The only thing I could see that was changed there was
Microsoft.NET.HostModelin edb0a06. That changed to writing out a temporary file for the bundle and then copying over the existing file (instead of updating it in place). I have no idea why that would change the start time on Windows, but it is the one thing I see that is shipped via the SDK and only affect single-file. That behaviour is also going to be changing with #116659 (cc @jtschuster)If there's some way to kick off a
crankjob with a local/patched SDK, we could see ifMicrosoft.NET.HostModelis actually the difference in those runs.agocke commentedon Jul 17, 2025
Based on the above, it sounds like this regression may be specific to the startup of a just created app. That is, the first run after the app has just been built. This isn't as high a priority as the startup time of a shipping application and they don't necessarily correspond due to differences in signing/virus scanning.
dotnet-policy-service commentedon Apr 8, 2026
Tagging subscribers to this area: @agocke, @jeffschwMSFT, @elinor-fung
See info in area-owners.md if you want to be subscribed.
elinor-fung commentedon Aug 10, 2026
I confirmed that this scenario is only first-run of a just-created app. It turns out this is from Windows Code Integrity checks. The perf lab machines appear to have that enabled, which would explain why I could not repro this locally as I do not. There is no effect on the startup of a deployed application (not just created on the machine). The cold start of a just-created app slows down, but the warm run of that is much improved. Closing this - real deployed apps are not affected and this is actually an improvement for just-created apps after the first run.
More details:
With Windows Code Integrity enabled, the entire image is hashed at startup (as part of CreateProcess). In order to do this, any dirty/modified pages must be flushed. The bundler was switched to writing the final single-file app via a file stream write rather than a memory-mapped write. The file stream write meant that much of the file is left dirty and must be reconciled on startup - with the memory-mapped write this was not necessary. However, the switch to the file stream write also meant that the results of the Code Integrity were cached - after the first run, the cost of the check goes away. With the memory-mapped write, the check was not cached and re-ran every app run. With copying over the app (as would be in shipped apps), this difference disappears - the check occurs on first-run and is cached.
Runs were all on aspnet-gold-win using the json scenario.
Running a just-created app:
With tracing on (ci.dll is Windows Code Integrity):
Running a copied app shows no difference - real deployment scenarios: